28 changed files with 912 additions and 341 deletions
@ -0,0 +1,67 @@ |
|||
// admin/newsletter.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'admin:newsletter'; |
|||
const express = require('express'); |
|||
|
|||
const { SiteController } = require('../../../lib/site-lib'); |
|||
|
|||
class NewsletterController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, DTP_COMPONENT_NAME); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const router = express.Router(); |
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = 'admin'; |
|||
res.locals.adminView = 'newsletter'; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('newsletterId', this.populateNewsletterId.bind(this)); |
|||
|
|||
router.get('/compose', this.getComposer.bind(this)); |
|||
|
|||
router.get('/:newsletterId', this.getComposer.bind(this)); |
|||
|
|||
router.get('/', this.getIndex.bind(this)); |
|||
|
|||
return router; |
|||
} |
|||
|
|||
async populateNewsletterId (req, res, next, newsletterId) { |
|||
const { newsletter: newsletterService } = this.dtp.services; |
|||
try { |
|||
res.locals.newsletter = await newsletterService.getById(newsletterId); |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate newsletterId', { newsletterId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getComposer (req, res) { |
|||
res.render('admin/newsletter/editor'); |
|||
} |
|||
|
|||
async getIndex (req, res, next) { |
|||
const { newsletter: newsletterService } = this.dtp.services; |
|||
try { |
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
res.locals.newsletters = await newsletterService.getNewsletters(res.locals.pagination); |
|||
res.render('admin/newsletter/index'); |
|||
} catch (error) { |
|||
return next(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = async (dtp) => { |
|||
let controller = new NewsletterController(dtp); |
|||
return controller; |
|||
}; |
@ -0,0 +1,45 @@ |
|||
// admin/page.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'admin:page'; |
|||
const express = require('express'); |
|||
|
|||
const { SiteController } = require('../../../lib/site-lib'); |
|||
|
|||
class PageController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, DTP_COMPONENT_NAME); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const router = express.Router(); |
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = 'admin'; |
|||
res.locals.adminView = 'page'; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('pageId', this.populatePageId.bind(this)); |
|||
|
|||
router.get('/', this.getIndex.bind(this)); |
|||
|
|||
return router; |
|||
} |
|||
|
|||
async populatePageId (req, res, next/*, pageId*/) { |
|||
return next(); |
|||
} |
|||
|
|||
async getIndex (req, res) { |
|||
res.render('admin/page/index'); |
|||
} |
|||
} |
|||
|
|||
module.exports = async (dtp) => { |
|||
let controller = new PageController(dtp); |
|||
return controller; |
|||
}; |
@ -0,0 +1,45 @@ |
|||
// admin/post.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'admin:post'; |
|||
const express = require('express'); |
|||
|
|||
const { SiteController } = require('../../../lib/site-lib'); |
|||
|
|||
class PostController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, DTP_COMPONENT_NAME); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const router = express.Router(); |
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = 'admin'; |
|||
res.locals.adminView = 'post'; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('postId', this.populatePostId.bind(this)); |
|||
|
|||
router.get('/', this.getIndex.bind(this)); |
|||
|
|||
return router; |
|||
} |
|||
|
|||
async populatePostId (req, res, next/*, postId*/) { |
|||
return next(); |
|||
} |
|||
|
|||
async getIndex (req, res) { |
|||
res.render('admin/post/index'); |
|||
} |
|||
} |
|||
|
|||
module.exports = async (dtp) => { |
|||
let controller = new PostController(dtp); |
|||
return controller; |
|||
}; |
@ -0,0 +1,100 @@ |
|||
// newsletter.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'newsletter'; |
|||
|
|||
const express = require('express'); |
|||
const multer = require('multer'); |
|||
|
|||
const { SiteController } = require('../../lib/site-lib'); |
|||
|
|||
class NewsletterController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, DTP_COMPONENT_NAME); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const { dtp } = this; |
|||
const { limiter: limiterService } = dtp.services; |
|||
|
|||
const upload = multer({ dest: '/tmp' }); |
|||
|
|||
const router = express.Router(); |
|||
dtp.app.use('/newsletter', router); |
|||
|
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = DTP_COMPONENT_NAME; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('newsletterId', this.populateNewsletterId.bind(this)); |
|||
|
|||
router.post('/', upload.none(), this.postAddRecipient.bind(this)); |
|||
|
|||
router.get('/:newsletterId', |
|||
limiterService.create(limiterService.config.newsletter.getView), |
|||
this.getView.bind(this), |
|||
); |
|||
|
|||
router.get('/', |
|||
limiterService.create(limiterService.config.newsletter.getIndex), |
|||
this.getIndex.bind(this), |
|||
); |
|||
} |
|||
|
|||
async populateNewsletterId (req, res, next, newsletterId) { |
|||
const { newsletter: newsletterService } = this.dtp.services; |
|||
try { |
|||
res.locals.newsletter = await newsletterService.getById(newsletterId); |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate newsletterId', { newsletterId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postAddRecipient (req, res) { |
|||
const { newsletter: newsletterService, displayEngine: displayEngineService } = this.dtp.services; |
|||
try { |
|||
const displayList = displayEngineService.createDisplayList('add-recipient'); |
|||
await newsletterService.addRecipient(req.body.email); |
|||
displayList.showNotification( |
|||
'You have been added to the newsletter. Please check your email and verify your email address.', |
|||
'success', |
|||
'bottom-center', |
|||
10000, |
|||
); |
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to update account settings', { error }); |
|||
return res.status(error.statusCode || 500).json({ |
|||
success: false, |
|||
message: error.message, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
async getView (req, res) { |
|||
res.render('newsletter/view'); |
|||
} |
|||
|
|||
async getIndex (req, res, next) { |
|||
const { newsletter: newsletterService } = this.dtp.services; |
|||
try { |
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
res.locals.newsletters = await newsletterService.getNewsletters(res.locals.pagination); |
|||
res.render('newsletter/index'); |
|||
} catch (error) { |
|||
return next(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = async (dtp) => { |
|||
let controller = new NewsletterController(dtp); |
|||
return controller; |
|||
}; |
@ -0,0 +1,15 @@ |
|||
// email-body.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
const Schema = mongoose.Schema; |
|||
|
|||
const EmailBodySchema = new Schema({ |
|||
subject: { type: String, required: true }, |
|||
body: { type: String, required: true }, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('EmailBody', EmailBodySchema); |
@ -0,0 +1,20 @@ |
|||
// email.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
const Schema = mongoose.Schema; |
|||
|
|||
const EmailSchema = new Schema({ |
|||
created: { type: Date, default: Date.now, required: true, index: -1 }, |
|||
token: { type: String, required: true, index: 1 }, |
|||
from: { type: String, required: true, }, |
|||
to: { type: String, required: true }, |
|||
to_lc: { type: String, required: true, lowercase: true, index: 1 }, |
|||
contentType: { type: String, enum: ['Newsletter'], required: true }, |
|||
content: { type: Schema.ObjectId, required: true, index: true, refPath: 'contentType' }, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('Email', EmailSchema); |
@ -0,0 +1,21 @@ |
|||
// newsletter-recipient.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
const Schema = mongoose.Schema; |
|||
|
|||
const NewsletterRecipientSchema = new Schema({ |
|||
created: { type: Date, default: Date.now, required: true, index: 1 }, |
|||
address: { type: String, required: true }, |
|||
address_lc: { type: String, required: true, lowercase: true, unique: true, index: 1 }, |
|||
flags: { |
|||
isVerified: { type: Boolean, default: false, required: true, index: 1 }, |
|||
isOptIn: { type: Boolean, default: false, required: true, index: 1 }, |
|||
isRejected: { type: Boolean, default: false, required: true, index: 1 }, |
|||
}, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('NewsletterRecipient', NewsletterRecipientSchema); |
@ -0,0 +1,31 @@ |
|||
// newsletter.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const Schema = mongoose.Schema; |
|||
|
|||
const NEWSLETTER_STATUS_LIST = ['draft', 'published', 'archived']; |
|||
|
|||
const NewsletterSchema = new Schema({ |
|||
created: { type: Date, default: Date.now, required: true, index: -1 }, |
|||
author: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, |
|||
title: { type: String, required: true }, |
|||
summary: { type: String }, |
|||
content: { |
|||
html: { type: String, required: true, select: false, }, |
|||
text: { type: String, required: true, select: false, }, |
|||
}, |
|||
status: { |
|||
type: String, |
|||
enum: NEWSLETTER_STATUS_LIST, |
|||
default: 'draft', |
|||
required: true, |
|||
index: true, |
|||
}, |
|||
}); |
|||
|
|||
module.exports = mongoose.model('Newsletter', NewsletterSchema); |
@ -0,0 +1,37 @@ |
|||
// markdown.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const fs = require('fs'); |
|||
|
|||
const { SiteService } = require('../../lib/site-lib'); |
|||
|
|||
const marked = require('marked'); |
|||
|
|||
class MarkdownService extends SiteService { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
this.markedRenderer = new marked.Renderer(); |
|||
} |
|||
|
|||
async renderMarkdownFile (documentFile) { |
|||
const markdown = await fs.promises.readFile(documentFile, 'utf8'); |
|||
return this.renderMarkdown(markdown); |
|||
} |
|||
|
|||
async renderMarkdown (markdown) { |
|||
return marked(markdown, { renderer: this.markedRenderer }); |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'markdown', |
|||
name: 'markdown', |
|||
create: (dtp) => { return new MarkdownService(dtp); }, |
|||
}; |
@ -0,0 +1,114 @@ |
|||
// newsletter.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const striptags = require('striptags'); |
|||
|
|||
const { SiteService } = require('../../lib/site-lib'); |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const Newsletter = mongoose.model('Newsletter'); |
|||
const NewsletterRecipient = mongoose.model('NewsletterRecipient'); |
|||
|
|||
class NewsletterService extends SiteService { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
|
|||
this.populateNewsletter = [ |
|||
{ |
|||
path: 'author', |
|||
select: '_id username username_lc displayName picture', |
|||
}, |
|||
]; |
|||
} |
|||
|
|||
async create (author, newsletterDefinition) { |
|||
const NOW = new Date(); |
|||
|
|||
const newsletter = new Newsletter(); |
|||
newsletter.created = NOW; |
|||
newsletter.author = author._id; |
|||
newsletter.title = striptags(newsletterDefinition.title.trim()); |
|||
newsletter.summary = striptags(newsletterDefinition.summary.trim()); |
|||
newsletter.content = newsletterDefinition.content.trim(); |
|||
newsletter.status = striptags(newsletterDefinition.status.trim().toLowerCase()); |
|||
|
|||
await newsletter.save(); |
|||
|
|||
return newsletter.toObject(); |
|||
} |
|||
|
|||
async update (newsletter, newsletterDefinition) { |
|||
const updateOp = { $set: { } }; |
|||
|
|||
if (newsletterDefinition.title) { |
|||
updateOp.$set.title = striptags(newsletterDefinition.title.trim()); |
|||
} |
|||
if (newsletterDefinition.summary) { |
|||
updateOp.$set.summary = striptags(newsletterDefinition.summary.trim()); |
|||
} |
|||
if (newsletterDefinition.content) { |
|||
updateOp.$set.content = newsletterDefinition.title.trim(); |
|||
} |
|||
if (newsletterDefinition.status) { |
|||
updateOp.$set.status = striptags(newsletterDefinition.status.trim()); |
|||
} |
|||
|
|||
if (Object.keys(updateOp.$set).length === 0) { |
|||
return; // no update to perform
|
|||
} |
|||
|
|||
await Newsletter.updateOne( |
|||
{ _id: newsletter._id }, |
|||
updateOp, |
|||
{ upsert: true }, |
|||
); |
|||
} |
|||
|
|||
async getNewsletters (pagination, status = ['published']) { |
|||
if (!Array.isArray(status)) { |
|||
status = [status]; |
|||
} |
|||
const newsletters = await Newsletter |
|||
.find({ status: { $in: status } }) |
|||
.sort({ created: -1 }) |
|||
.skip(pagination.skip) |
|||
.limit(pagination.cpp) |
|||
.lean(); |
|||
return newsletters; |
|||
} |
|||
|
|||
async getById (newsletterId) { |
|||
const newsletter = await Newsletter |
|||
.findById(newsletterId) |
|||
.select('+content') |
|||
.populate(this.populateNewsletter) |
|||
.lean(); |
|||
return newsletter; |
|||
} |
|||
|
|||
async addRecipient (emailAddress) { |
|||
const { email: emailService } = this.dtp.services; |
|||
const NOW = new Date(); |
|||
|
|||
await emailService.checkEmailAddress(emailAddress); |
|||
|
|||
const recipient = new NewsletterRecipient(); |
|||
recipient.created = NOW; |
|||
recipient.address = striptags(emailAddress.trim()); |
|||
recipient.address_lc = recipient.address.toLowerCase(); |
|||
await recipient.save(); |
|||
|
|||
return recipient.toObject(); |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'newsletter', |
|||
name: 'newsletter', |
|||
create: (dtp) => { return new NewsletterService(dtp); }, |
|||
}; |
@ -0,0 +1,59 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
- var actionUrl = newsletter ? `/admin/newsletter/${newsletter._id}` : `/admin/newsletter`; |
|||
|
|||
form(method="POST", action= actionUrl).uk-form |
|||
.uk-margin |
|||
label(for="title").uk-form-label.sr-only Newsletter title |
|||
input(id="title", name="title", type="text", placeholder= "Enter newsletter title", value= newsletter ? newsletter.title : undefined).uk-input |
|||
|
|||
.uk-margin |
|||
label(for="content-html").uk-form-label.sr-only Newsletter HTML body |
|||
textarea(id="content-html", name="content.html", rows="4").uk-textarea= newsletter ? newsletter.content.html : undefined |
|||
|
|||
.uk-margin |
|||
button(type="button", onclick="return dtp.app.copyHtmlToText(event, 'content-text');").uk-button.dtp-button-default Copy HTML to Text |
|||
|
|||
.uk-margin |
|||
label(for="content-text").uk-form-label.sr-only Newsletter text body |
|||
textarea(id="content-text", name="content.text", rows="4", placeholder= "Enter text-only version of newsletter.").uk-textarea= newsletter ? newsletter.content.text : undefined |
|||
|
|||
.uk-margin |
|||
label(for="summary").uk-form-label.sr-only Newsletter summary |
|||
textarea(id="summary", name="summary", rows="4", placeholder= "Enter newsletter summary (text only, no HTML)").uk-textarea= newsletter ? newsletter.summary : undefined |
|||
|
|||
button(type="submit").uk-button.dtp-button-primary= newsletter ? 'Update newsletter' : 'Save newsletter' |
|||
|
|||
block viewjs |
|||
script(src="/tinymce/tinymce.min.js") |
|||
script. |
|||
window.addEventListener('dtp-load', async ( ) => { |
|||
const toolbarItems = [ |
|||
'undo redo', |
|||
'formatselect', |
|||
'bold italic backcolor', |
|||
'alignleft aligncenter alignright alignjustify', |
|||
'bullist numlist outdent indent removeformat', |
|||
'link image', |
|||
'help' |
|||
]; |
|||
const pluginItems = [ |
|||
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'print', |
|||
'preview', 'anchor', 'searchreplace', 'visualblocks', 'code', |
|||
'fullscreen', 'insertdatetime', 'media', 'table', 'paste', 'code', |
|||
'help', 'wordcount', |
|||
] |
|||
|
|||
const editors = await tinymce.init({ |
|||
selector: 'textarea#content-html', |
|||
height: 500, |
|||
menubar: false, |
|||
plugins: pluginItems.join(' '), |
|||
toolbar: toolbarItems.join('|'), |
|||
branding: false, |
|||
images_upload_url: '/image/tinymce', |
|||
}); |
|||
|
|||
window.dtp.app.editor = editors[0]; |
|||
}); |
@ -0,0 +1,21 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
.uk-margin |
|||
div(uk-grid) |
|||
.uk-width-expand |
|||
h1 Newsletters |
|||
.uk-width-auto |
|||
a(href="/admin/newsletter/compose").uk-button.dtp-button-primary |
|||
span |
|||
i.fas.fa-plus |
|||
span.uk-margin-small-left New Newsletter |
|||
|
|||
.uk-margin |
|||
if (Array.isArray(newsletters) && (newsletters.length > 0)) |
|||
ul.uk-list |
|||
each newsletter in newsletters |
|||
li |
|||
a(href=`/admin/newsletter/${newsletter._id}`)= newsletter.title |
|||
else |
|||
div There are no newsletters at this time. |
@ -0,0 +1,15 @@ |
|||
extends ../layouts/main |
|||
block content |
|||
|
|||
section.uk-section.uk-section-default |
|||
.uk-container |
|||
|
|||
h1 #{site.name} Newsletters |
|||
|
|||
if Array.isArray(newsletters) && (newsletters.length > 0) |
|||
ul.uk-list |
|||
each newsletter of newsletters |
|||
li |
|||
a(href=`/newsletter/${newsletter._id}`).uk-link-reset= newsletter.title |
|||
else |
|||
div There are no newsletters at this time. Please check back later. |
@ -0,0 +1,113 @@ |
|||
// newsletter.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// All Rights Reserved
|
|||
|
|||
'use strict'; |
|||
|
|||
const DTP_COMPONENT_NAME = 'newsletter'; |
|||
|
|||
const path = require('path'); |
|||
require('dotenv').config({ path: path.resolve(__dirname, '..', '..', '.env') }); |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const { SitePlatform, SiteLog } = require(path.join(__dirname, '..', '..', 'lib', 'site-lib')); |
|||
|
|||
module.pkg = require(path.resolve(__dirname, '..', '..', 'package.json')); |
|||
module.config = { |
|||
componentName: DTP_COMPONENT_NAME, |
|||
root: path.resolve(__dirname, '..', '..'), |
|||
}; |
|||
|
|||
module.log = new SiteLog(module, module.config.componentName); |
|||
|
|||
module.sendNewsletter = async (job) => { |
|||
|
|||
module.log.info('newsletter email job received', { data: job.data }); |
|||
|
|||
const NewsletterRecipient = mongoose.model('NewsletterRecipient'); |
|||
|
|||
try { |
|||
/* |
|||
* Create one Bull Queue job per email to be delivered. |
|||
*/ |
|||
await NewsletterRecipient |
|||
.find({ 'flags.isVerified': true, 'flags.isOptIn': true, 'flags.isRejected': false }) |
|||
.lean() |
|||
.cursor() |
|||
.eachAsync(async (recipient) => { |
|||
try { |
|||
const jobData = { |
|||
newsletterId: job.data.newsletterId, |
|||
recipient: recipient.address, |
|||
}; |
|||
const jobOptions = { |
|||
attempts: 3, |
|||
}; |
|||
await module.jobQueue.add('email-send', jobData, jobOptions); |
|||
} catch (error) { |
|||
module.log.error('failed to create newsletter email job'); |
|||
// but continue
|
|||
} |
|||
}, { parallel: 4 }); |
|||
} catch (error) { |
|||
module.log('failed to send newsletter', { newsletterId: job.data.newsletterId, error }); |
|||
throw error; // throw error to Bull so it can report in job reports
|
|||
} |
|||
}; |
|||
|
|||
module.sendNewsletterEmail = async (job) => { |
|||
const { newsletter: newsletterService, email: emailService } = module.services; |
|||
const { newsletterId, recipient } = job.data; |
|||
try { |
|||
|
|||
let newsletter = module.newsletters[newsletterId]; |
|||
|
|||
if (!newsletter) { |
|||
newsletter = await newsletterService.getById(newsletterId); |
|||
module.newsletters[newsletterId] = newsletter; //TODO: clean up memory leak of newsletter (remove when all emails are sent)
|
|||
} |
|||
|
|||
if (!newsletter) { |
|||
throw new Error('newsletter not found'); |
|||
} |
|||
|
|||
const response = await emailService.send({ |
|||
from: '[email protected]', |
|||
to: recipient, |
|||
subject: newsletter.title, |
|||
html: newsletter.content.html, |
|||
text: newsletter.content.text, |
|||
}); |
|||
|
|||
job.log(`newsletter email sent: ${response}`); |
|||
} catch (error) { |
|||
module.error('failed to send newsletter email', { newsletterId, recipient, error }); |
|||
throw error; // throw error to Bull so it can report in job reports
|
|||
} |
|||
}; |
|||
|
|||
(async ( ) => { |
|||
try { |
|||
/* |
|||
* Platform startup |
|||
*/ |
|||
await SitePlatform.startPlatform(module); |
|||
|
|||
const { jobQueue: jobQueueService } = module.services; |
|||
|
|||
module.jobQueue = await jobQueueService.getJobQueue('newsletter', { |
|||
attempts: 3, |
|||
}); |
|||
module.jobQueue.process('email', module.sendNewsletter); |
|||
module.jobQueue.process('email-send', module.sendNewsletterEmail); |
|||
|
|||
/* |
|||
* Worker startup |
|||
*/ |
|||
module.log.info(`${module.pkg.name} v${module.pkg.version} Newsletter worker started`); |
|||
} catch (error) { |
|||
module.log.error('failed to start Newsletter worker', { error }); |
|||
process.exit(-1); |
|||
} |
|||
})(); |
@ -0,0 +1,40 @@ |
|||
# DTP Sites: Services |
|||
|
|||
Services are common logic implemented in a centralized location made accessible to the rest of the application to perform tasks in a common way. They live in [app/services](app/services), and are scripts that export a specific structure that identifies the service and provides a way to create, start, and stop them. |
|||
|
|||
Services can't reference each other in their constructors, but they can reference each other in their `start` method with the caveat that the service you are referencing may not have had it's `start` method called, yet. DTP loads services in one loop, then starts them in a separate loop after they are loaded. |
|||
|
|||
All other service methods implemented can reference all other services with the assumption that they are started and ready to provide full service. |
|||
|
|||
```js |
|||
// myservice.js |
|||
// Copyright (C) 2021 Digital Telepresence, LLC |
|||
// License: Apache-2.0 |
|||
|
|||
'use strict'; |
|||
|
|||
const { SiteService } = require('../../lib/site-lib'); |
|||
|
|||
class MyService extends SiteService { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
/* |
|||
* perform service initialization here |
|||
*/ |
|||
} |
|||
|
|||
/* |
|||
* Implement service methods here |
|||
*/ |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'my-service', |
|||
name: 'myService', |
|||
create: (dtp) => { return new MyService(dtp); }, |
|||
}; |
|||
``` |
@ -1265,13 +1265,6 @@ [email protected]: |
|||
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" |
|||
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= |
|||
|
|||
agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0: |
|||
version "4.3.0" |
|||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" |
|||
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== |
|||
dependencies: |
|||
es6-promisify "^5.0.0" |
|||
|
|||
agent-base@6: |
|||
version "6.0.2" |
|||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" |
|||
@ -1279,13 +1272,6 @@ agent-base@6: |
|||
dependencies: |
|||
debug "4" |
|||
|
|||
agent-base@~4.2.1: |
|||
version "4.2.1" |
|||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" |
|||
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== |
|||
dependencies: |
|||
es6-promisify "^5.0.0" |
|||
|
|||
ajv-keywords@^3.5.2: |
|||
version "3.5.2" |
|||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" |
|||
@ -1547,13 +1533,6 @@ assign-symbols@^1.0.0: |
|||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" |
|||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= |
|||
|
|||
[email protected]: |
|||
version "0.14.2" |
|||
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" |
|||
integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== |
|||
dependencies: |
|||
tslib "^2.0.1" |
|||
|
|||
async-done@^1.2.0, async-done@^1.2.2: |
|||
version "1.3.2" |
|||
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" |
|||
@ -1591,7 +1570,7 @@ [email protected]: |
|||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" |
|||
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= |
|||
|
|||
async@^2.1.1, async@^2.6.1: |
|||
async@^2.1.1: |
|||
version "2.6.3" |
|||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" |
|||
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== |
|||
@ -2301,7 +2280,7 @@ colors@^1.1.2, colors@^1.2.1: |
|||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" |
|||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== |
|||
|
|||
combined-stream@^1.0.6, combined-stream@^1.0.8: |
|||
combined-stream@^1.0.8: |
|||
version "1.0.8" |
|||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" |
|||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== |
|||
@ -2574,11 +2553,6 @@ d@1, d@^1.0.1: |
|||
es5-ext "^0.10.50" |
|||
type "^1.0.1" |
|||
|
|||
data-uri-to-buffer@1: |
|||
version "1.2.0" |
|||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" |
|||
integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ== |
|||
|
|||
data-urls@^3.0.1: |
|||
version "3.0.1" |
|||
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.1.tgz#597fc2ae30f8bc4dbcf731fcd1b1954353afc6f8" |
|||
@ -2593,7 +2567,7 @@ date-now@^0.1.4: |
|||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" |
|||
integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= |
|||
|
|||
debug@2, [email protected], debug@^2.2.0, debug@^2.3.3, debug@~2.6.4: |
|||
[email protected], debug@^2.2.0, debug@^2.3.3, debug@~2.6.4: |
|||
version "2.6.9" |
|||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" |
|||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== |
|||
@ -2621,7 +2595,7 @@ [email protected]: |
|||
dependencies: |
|||
ms "2.1.2" |
|||
|
|||
debug@^3.1.0, debug@^3.2.6, debug@^3.2.7: |
|||
debug@^3.2.6, debug@^3.2.7: |
|||
version "3.2.7" |
|||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" |
|||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== |
|||
@ -2730,15 +2704,6 @@ define-property@^2.0.2: |
|||
is-descriptor "^1.0.2" |
|||
isobject "^3.0.1" |
|||
|
|||
degenerator@^1.0.4: |
|||
version "1.0.4" |
|||
resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" |
|||
integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= |
|||
dependencies: |
|||
ast-types "0.x.x" |
|||
escodegen "1.x.x" |
|||
esprima "3.x.x" |
|||
|
|||
delayed-stream@~1.0.0: |
|||
version "1.0.0" |
|||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" |
|||
@ -3146,18 +3111,6 @@ es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3: |
|||
es5-ext "^0.10.35" |
|||
es6-symbol "^3.1.1" |
|||
|
|||
es6-promise@^4.0.3: |
|||
version "4.2.8" |
|||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" |
|||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== |
|||
|
|||
es6-promisify@^5.0.0: |
|||
version "5.0.0" |
|||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" |
|||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= |
|||
dependencies: |
|||
es6-promise "^4.0.3" |
|||
|
|||
es6-symbol@^3.1.1, es6-symbol@~3.1.3: |
|||
version "3.1.3" |
|||
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" |
|||
@ -3196,18 +3149,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: |
|||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" |
|||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= |
|||
|
|||
[email protected]: |
|||
version "1.14.3" |
|||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" |
|||
integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== |
|||
dependencies: |
|||
esprima "^4.0.1" |
|||
estraverse "^4.2.0" |
|||
esutils "^2.0.2" |
|||
optionator "^0.8.1" |
|||
optionalDependencies: |
|||
source-map "~0.6.1" |
|||
|
|||
escodegen@^2.0.0: |
|||
version "2.0.0" |
|||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" |
|||
@ -3228,11 +3169,6 @@ [email protected]: |
|||
esrecurse "^4.3.0" |
|||
estraverse "^4.1.1" |
|||
|
|||
[email protected]: |
|||
version "3.1.3" |
|||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" |
|||
integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= |
|||
|
|||
esprima@^4.0.1: |
|||
version "4.0.1" |
|||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" |
|||
@ -3245,7 +3181,7 @@ esrecurse@^4.3.0: |
|||
dependencies: |
|||
estraverse "^5.2.0" |
|||
|
|||
estraverse@^4.1.1, estraverse@^4.2.0: |
|||
estraverse@^4.1.1: |
|||
version "4.3.0" |
|||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" |
|||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== |
|||
@ -3409,7 +3345,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: |
|||
assign-symbols "^1.0.0" |
|||
is-extendable "^1.0.1" |
|||
|
|||
extend@^3.0.0, extend@~3.0.2: |
|||
extend@^3.0.0: |
|||
version "3.0.2" |
|||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" |
|||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== |
|||
@ -3479,7 +3415,7 @@ feed@^4.2.2: |
|||
dependencies: |
|||
xml-js "^1.6.11" |
|||
|
|||
file-uri-to-path@1, [email protected]: |
|||
[email protected]: |
|||
version "1.0.0" |
|||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" |
|||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== |
|||
@ -3616,15 +3552,6 @@ foreach@^2.0.5: |
|||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" |
|||
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= |
|||
|
|||
form-data@^2.3.3: |
|||
version "2.5.1" |
|||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" |
|||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== |
|||
dependencies: |
|||
asynckit "^0.4.0" |
|||
combined-stream "^1.0.6" |
|||
mime-types "^2.1.12" |
|||
|
|||
form-data@^4.0.0: |
|||
version "4.0.0" |
|||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" |
|||
@ -3701,14 +3628,6 @@ fsevents@~2.3.2: |
|||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" |
|||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== |
|||
|
|||
ftp@~0.3.10: |
|||
version "0.3.10" |
|||
resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" |
|||
integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= |
|||
dependencies: |
|||
readable-stream "1.1.x" |
|||
xregexp "2.0.0" |
|||
|
|||
function-bind@^1.1.1: |
|||
version "1.1.1" |
|||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" |
|||
@ -3802,18 +3721,6 @@ get-symbol-description@^1.0.0: |
|||
call-bind "^1.0.2" |
|||
get-intrinsic "^1.1.1" |
|||
|
|||
get-uri@^2.0.0: |
|||
version "2.0.4" |
|||
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.4.tgz#d4937ab819e218d4cb5ae18e4f5962bef169cc6a" |
|||
integrity sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q== |
|||
dependencies: |
|||
data-uri-to-buffer "1" |
|||
debug "2" |
|||
extend "~3.0.2" |
|||
file-uri-to-path "1" |
|||
ftp "~0.3.10" |
|||
readable-stream "2" |
|||
|
|||
get-value@^2.0.3, get-value@^2.0.6: |
|||
version "2.0.6" |
|||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" |
|||
@ -4236,14 +4143,6 @@ http-errors@~1.7.2: |
|||
statuses ">= 1.5.0 < 2" |
|||
toidentifier "1.0.0" |
|||
|
|||
http-proxy-agent@^2.1.0: |
|||
version "2.1.0" |
|||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" |
|||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== |
|||
dependencies: |
|||
agent-base "4" |
|||
debug "3.1.0" |
|||
|
|||
http-proxy-agent@^5.0.0: |
|||
version "5.0.0" |
|||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" |
|||
@ -4262,14 +4161,6 @@ http-proxy@^1.18.1: |
|||
follow-redirects "^1.0.0" |
|||
requires-port "^1.0.0" |
|||
|
|||
https-proxy-agent@^3.0.0: |
|||
version "3.0.1" |
|||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" |
|||
integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== |
|||
dependencies: |
|||
agent-base "^4.3.0" |
|||
debug "^3.1.0" |
|||
|
|||
https-proxy-agent@^5.0.0: |
|||
version "5.0.0" |
|||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" |
|||
@ -4337,16 +4228,6 @@ [email protected]: |
|||
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" |
|||
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= |
|||
|
|||
inflection@~1.12.0: |
|||
version "1.12.0" |
|||
resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" |
|||
integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY= |
|||
|
|||
inflection@~1.3.0: |
|||
version "1.3.8" |
|||
resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.3.8.tgz#cbd160da9f75b14c3cc63578d4f396784bf3014e" |
|||
integrity sha1-y9Fg2p91sUw8xjV41POWeEvzAU4= |
|||
|
|||
inflight@^1.0.4: |
|||
version "1.0.6" |
|||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" |
|||
@ -4420,11 +4301,6 @@ ip-address@^5.8.9: |
|||
lodash "^4.17.15" |
|||
sprintf-js "1.1.2" |
|||
|
|||
[email protected], ip@^1.1.5: |
|||
version "1.1.5" |
|||
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" |
|||
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= |
|||
|
|||
[email protected]: |
|||
version "1.9.1" |
|||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" |
|||
@ -4754,11 +4630,6 @@ is-shared-array-buffer@^1.0.1: |
|||
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" |
|||
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== |
|||
|
|||
is-stream@^1.1.0: |
|||
version "1.1.0" |
|||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" |
|||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= |
|||
|
|||
is-stream@^2.0.0: |
|||
version "2.0.1" |
|||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" |
|||
@ -5295,13 +5166,6 @@ lowercase-keys@^2.0.0: |
|||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" |
|||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== |
|||
|
|||
lru-cache@^5.1.1: |
|||
version "5.1.1" |
|||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" |
|||
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== |
|||
dependencies: |
|||
yallist "^3.0.2" |
|||
|
|||
lru-cache@^6.0.0: |
|||
version "6.0.0" |
|||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" |
|||
@ -5316,21 +5180,6 @@ magic-string@^0.25.0, magic-string@^0.25.7: |
|||
dependencies: |
|||
sourcemap-codec "^1.4.4" |
|||
|
|||
mailgun-js@^0.22.0: |
|||
version "0.22.0" |
|||
resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.22.0.tgz#128942b5e47a364a470791608852bf68c96b3a05" |
|||
integrity sha512-a2alg5nuTZA9Psa1pSEIEsbxr1Zrmqx4VkgGCQ30xVh0kIH7Bu57AYILo+0v8QLSdXtCyLaS+KVmdCrQo0uWFA== |
|||
dependencies: |
|||
async "^2.6.1" |
|||
debug "^4.1.0" |
|||
form-data "^2.3.3" |
|||
inflection "~1.12.0" |
|||
is-stream "^1.1.0" |
|||
path-proxy "~1.0.0" |
|||
promisify-call "^2.0.2" |
|||
proxy-agent "^3.0.3" |
|||
tsscmp "^1.0.6" |
|||
|
|||
make-dir@^2.1.0: |
|||
version "2.1.0" |
|||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" |
|||
@ -5700,11 +5549,6 @@ neo-async@^2.6.2: |
|||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" |
|||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== |
|||
|
|||
netmask@^1.0.6: |
|||
version "1.0.6" |
|||
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" |
|||
integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= |
|||
|
|||
next-tick@~1.0.0: |
|||
version "1.0.0" |
|||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" |
|||
@ -5734,6 +5578,11 @@ node-releases@^2.0.1: |
|||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" |
|||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== |
|||
|
|||
nodemailer@^6.7.2: |
|||
version "6.7.2" |
|||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" |
|||
integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== |
|||
|
|||
nodemon@^2.0.2: |
|||
version "2.0.15" |
|||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" |
|||
@ -6026,31 +5875,6 @@ p-try@^2.0.0: |
|||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" |
|||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== |
|||
|
|||
pac-proxy-agent@^3.0.1: |
|||
version "3.0.1" |
|||
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz#115b1e58f92576cac2eba718593ca7b0e37de2ad" |
|||
integrity sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ== |
|||
dependencies: |
|||
agent-base "^4.2.0" |
|||
debug "^4.1.1" |
|||
get-uri "^2.0.0" |
|||
http-proxy-agent "^2.1.0" |
|||
https-proxy-agent "^3.0.0" |
|||
pac-resolver "^3.0.0" |
|||
raw-body "^2.2.0" |
|||
socks-proxy-agent "^4.0.1" |
|||
|
|||
pac-resolver@^3.0.0: |
|||
version "3.0.0" |
|||
resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" |
|||
integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== |
|||
dependencies: |
|||
co "^4.6.0" |
|||
degenerator "^1.0.4" |
|||
ip "^1.1.5" |
|||
netmask "^1.0.6" |
|||
thunkify "^2.1.2" |
|||
|
|||
package-json@^6.3.0: |
|||
version "6.5.0" |
|||
resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" |
|||
@ -6169,13 +5993,6 @@ path-parse@^1.0.6: |
|||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" |
|||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== |
|||
|
|||
path-proxy@~1.0.0: |
|||
version "1.0.0" |
|||
resolved "https://registry.yarnpkg.com/path-proxy/-/path-proxy-1.0.0.tgz#18e8a36859fc9d2f1a53b48dee138543c020de5e" |
|||
integrity sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4= |
|||
dependencies: |
|||
inflection "~1.3.0" |
|||
|
|||
path-root-regex@^0.1.0: |
|||
version "0.1.2" |
|||
resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" |
|||
@ -6334,13 +6151,6 @@ promise@^7.0.1: |
|||
dependencies: |
|||
asap "~2.0.3" |
|||
|
|||
promisify-call@^2.0.2: |
|||
version "2.0.4" |
|||
resolved "https://registry.yarnpkg.com/promisify-call/-/promisify-call-2.0.4.tgz#d48c2d45652ccccd52801ddecbd533a6d4bd5fba" |
|||
integrity sha1-1IwtRWUszM1SgB3ey9UzptS9X7o= |
|||
dependencies: |
|||
with-callback "^1.0.2" |
|||
|
|||
proxy-addr@~2.0.5: |
|||
version "2.0.7" |
|||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" |
|||
@ -6349,25 +6159,6 @@ proxy-addr@~2.0.5: |
|||
forwarded "0.2.0" |
|||
ipaddr.js "1.9.1" |
|||
|
|||
proxy-agent@^3.0.3: |
|||
version "3.1.1" |
|||
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.1.1.tgz#7e04e06bf36afa624a1540be247b47c970bd3014" |
|||
integrity sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw== |
|||
dependencies: |
|||
agent-base "^4.2.0" |
|||
debug "4" |
|||
http-proxy-agent "^2.1.0" |
|||
https-proxy-agent "^3.0.0" |
|||
lru-cache "^5.1.1" |
|||
pac-proxy-agent "^3.0.1" |
|||
proxy-from-env "^1.0.0" |
|||
socks-proxy-agent "^4.0.1" |
|||
|
|||
proxy-from-env@^1.0.0: |
|||
version "1.1.0" |
|||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" |
|||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== |
|||
|
|||
prr@~1.0.1: |
|||
version "1.0.1" |
|||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" |
|||
@ -6580,7 +6371,7 @@ [email protected]: |
|||
iconv-lite "0.4.24" |
|||
unpipe "1.0.0" |
|||
|
|||
raw-body@^2.2.0, raw-body@^2.3.2: |
|||
raw-body@^2.3.2: |
|||
version "2.4.2" |
|||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" |
|||
integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== |
|||
@ -6654,7 +6445,16 @@ [email protected]: |
|||
isarray "0.0.1" |
|||
string_decoder "~0.10.x" |
|||
|
|||
readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: |
|||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0: |
|||
version "3.6.0" |
|||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" |
|||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== |
|||
dependencies: |
|||
inherits "^2.0.3" |
|||
string_decoder "^1.1.1" |
|||
util-deprecate "^1.0.1" |
|||
|
|||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: |
|||
version "2.3.7" |
|||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" |
|||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== |
|||
@ -6667,15 +6467,6 @@ readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stre |
|||
string_decoder "~1.1.1" |
|||
util-deprecate "~1.0.1" |
|||
|
|||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0: |
|||
version "3.6.0" |
|||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" |
|||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== |
|||
dependencies: |
|||
inherits "^2.0.3" |
|||
string_decoder "^1.1.1" |
|||
util-deprecate "^1.0.1" |
|||
|
|||
readdirp@^2.2.1: |
|||
version "2.2.1" |
|||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" |
|||
@ -7281,11 +7072,6 @@ slug@^5.1.0: |
|||
resolved "https://registry.yarnpkg.com/slug/-/slug-5.1.0.tgz#8a7e30ca1c3a6dc40cf74e269750913a865edb0b" |
|||
integrity sha512-IS39jKR6m+puU8zWgH6ruwx1sfzFNJ6Ai5PKIlUqd0X8C3ca7PB49Cvm0uayqgEt1jgaojO2wWEsQJngnh7fDA== |
|||
|
|||
smart-buffer@^4.1.0: |
|||
version "4.2.0" |
|||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" |
|||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== |
|||
|
|||
snapdragon-node@^2.0.1: |
|||
version "2.1.1" |
|||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" |
|||
@ -7414,22 +7200,6 @@ socket.io@^4.4.0: |
|||
socket.io-adapter "~2.3.3" |
|||
socket.io-parser "~4.0.4" |
|||
|
|||
socks-proxy-agent@^4.0.1: |
|||
version "4.0.2" |
|||
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" |
|||
integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== |
|||
dependencies: |
|||
agent-base "~4.2.1" |
|||
socks "~2.3.2" |
|||
|
|||
socks@~2.3.2: |
|||
version "2.3.3" |
|||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" |
|||
integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA== |
|||
dependencies: |
|||
ip "1.1.5" |
|||
smart-buffer "^4.1.0" |
|||
|
|||
source-list-map@^2.0.0: |
|||
version "2.0.1" |
|||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" |
|||
@ -7878,11 +7648,6 @@ through@^2.3.8: |
|||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" |
|||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= |
|||
|
|||
thunkify@^2.1.2: |
|||
version "2.1.2" |
|||
resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" |
|||
integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= |
|||
|
|||
time-stamp@^1.0.0: |
|||
version "1.1.0" |
|||
resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" |
|||
@ -8005,16 +7770,11 @@ tr46@~0.0.3: |
|||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" |
|||
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= |
|||
|
|||
tslib@^2.0.1, tslib@^2.3.0: |
|||
tslib@^2.3.0: |
|||
version "2.3.1" |
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" |
|||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== |
|||
|
|||
tsscmp@^1.0.6: |
|||
version "1.0.6" |
|||
resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" |
|||
integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== |
|||
|
|||
tunnel-agent@^0.6.0: |
|||
version "0.6.0" |
|||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" |
|||
@ -8578,11 +8338,6 @@ widest-line@^3.1.0: |
|||
dependencies: |
|||
string-width "^4.0.0" |
|||
|
|||
with-callback@^1.0.2: |
|||
version "1.0.2" |
|||
resolved "https://registry.yarnpkg.com/with-callback/-/with-callback-1.0.2.tgz#a09629b9a920028d721404fb435bdcff5c91bc21" |
|||
integrity sha1-oJYpuakgAo1yFAT7Q1vc/1yRvCE= |
|||
|
|||
with@^7.0.0: |
|||
version "7.0.2" |
|||
resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" |
|||
@ -8870,11 +8625,6 @@ xmlhttprequest-ssl@~1.6.2: |
|||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" |
|||
integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== |
|||
|
|||
[email protected]: |
|||
version "2.0.0" |
|||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" |
|||
integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= |
|||
|
|||
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: |
|||
version "4.0.2" |
|||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" |
|||
@ -8895,11 +8645,6 @@ y18n@^5.0.5: |
|||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" |
|||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== |
|||
|
|||
yallist@^3.0.2: |
|||
version "3.1.1" |
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" |
|||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== |
|||
|
|||
yallist@^4.0.0: |
|||
version "4.0.0" |
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" |
|||
|
Loading…
Reference in new issue