diff --git a/.env.default b/.env.default index 1c40be45..bffa96ab 100644 --- a/.env.default +++ b/.env.default @@ -22,7 +22,7 @@ MAILGUN_DOMAIN= # MONGODB_HOST=localhost:27017 -MONGODB_DATABASE=dtp-sites +MONGODB_DATABASE=dtp-webapp # # Redis configuration @@ -39,10 +39,10 @@ REDIS_PASSWORD= MINIO_ENDPOINT=localhost MINIO_PORT=9000 MINIO_USE_SSL=disabled -MINIO_ACCESS_KEY=dtp-sites +MINIO_ACCESS_KEY=dtp-webapp MINIO_SECRET_KEY= -MINIO_IMAGE_BUCKET=site-images -MINIO_VIDEO_BUCKET=site-videos +MINIO_IMAGE_BUCKET=webapp-images +MINIO_VIDEO_BUCKET=webapp-videos # # ExpressJS/HTTP configuration @@ -60,9 +60,9 @@ DTP_LOG_CONSOLE=enabled DTP_LOG_MONGODB=enabled DTP_LOG_FILE=enabled -DTP_LOG_FILE_PATH=/tmp/dtp-sites/logs -DTP_LOG_FILE_NAME_APP=justjoeradio-app.log -DTP_LOG_FILE_NAME_HTTP=justjoeradio-access.log +DTP_LOG_FILE_PATH=/tmp/dtp-webapp/logs +DTP_LOG_FILE_NAME_APP=webapp-app.log +DTP_LOG_FILE_NAME_HTTP=webapp-access.log DTP_LOG_DEBUG=enabled DTP_LOG_INFO=enabled diff --git a/.vscode/launch.json b/.vscode/launch.json index 1645c2b4..c702308a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,11 +7,11 @@ { "type": "pwa-node", "request": "launch", - "name": "web:dtp-sites", + "name": "web:dtp-webapp", "skipFiles": [ "/**" ], - "program": "${workspaceFolder:dtp-justjoeradio}/dtp-sites.js", + "program": "${workspaceFolder:dtp-sites}/dtp-sites.js", "console": "integratedTerminal", "env": { "HTTP_BIND_PORT": "3333" @@ -20,13 +20,13 @@ { "type": "pwa-node", "request": "launch", - "name": "cli:dtp-sites", + "name": "cli:dtp-webapp", "skipFiles": [ "/**" ], - "program": "${workspaceFolder:dtp-justjoeradio}/dtp-sites-cli.js", + "program": "${workspaceFolder:dtp-sites}/dtp-sites-cli.js", "console": "integratedTerminal", "args": ["--action=reset-indexes", "all"] } ] -} +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 5c062691..57a6f27f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2021 Digital Telepresence, LLC +Copyright 2022 DTP Technologies, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. diff --git a/NOTES.md b/NOTES.md index 4bed709f..beae5fef 100644 --- a/NOTES.md +++ b/NOTES.md @@ -2,8 +2,6 @@ - Password - Light mode - - Want to tune in live Want chat diff --git a/app/controllers/admin.js b/app/controllers/admin.js index 5fdc4e89..0ce59849 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -1,5 +1,5 @@ // admin.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -45,12 +45,10 @@ class AdminController extends SiteController { ); router.use('/content-report',await this.loadChild(path.join(__dirname, 'admin', 'content-report'))); + router.use('/core-node',await this.loadChild(path.join(__dirname, 'admin', 'core-node'))); router.use('/host',await this.loadChild(path.join(__dirname, 'admin', 'host'))); router.use('/job-queue', await this.loadChild(path.join(__dirname, 'admin', 'job-queue'))); router.use('/log', await this.loadChild(path.join(__dirname, 'admin', 'log'))); - router.use('/newsletter', await this.loadChild(path.join(__dirname, 'admin', 'newsletter'))); - router.use('/page', await this.loadChild(path.join(__dirname, 'admin', 'page'))); - router.use('/post', await this.loadChild(path.join(__dirname, 'admin', 'post'))); router.use('/settings', await this.loadChild(path.join(__dirname, 'admin', 'settings'))); router.use('/user', await this.loadChild(path.join(__dirname, 'admin', 'user'))); diff --git a/app/controllers/admin/content-report.js b/app/controllers/admin/content-report.js index f15ad7f8..61db82d2 100644 --- a/app/controllers/admin/content-report.js +++ b/app/controllers/admin/content-report.js @@ -1,5 +1,5 @@ // admin/content-report.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/admin/core-node.js b/app/controllers/admin/core-node.js new file mode 100644 index 00000000..3941536b --- /dev/null +++ b/app/controllers/admin/core-node.js @@ -0,0 +1,60 @@ +// admin/content-report.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'admin:content-report'; + +const express = require('express'); +// const multer = require('multer'); + +const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib'); + +class CoreNodeController extends SiteController { + + constructor (dtp) { + super(dtp, DTP_COMPONENT_NAME); + } + + async start ( ) { + // const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/upload` }); + + const router = express.Router(); + router.use(async (req, res, next) => { + res.locals.currentView = 'admin'; + res.locals.adminView = 'core-node'; + return next(); + }); + + router.post('/connect', this.postCoreNodeConnect.bind(this)); + router.get('/connect', this.getCoreNodeConnectForm.bind(this)); + + router.get('/', this.getIndex.bind(this)); + + return router; + } + + async postCoreNodeConnect (req, res, next) { + // const { coreNode: coreNodeService } = this.dtp.services; + try { + + } catch (error) { + this.log.error('failed to create Core Node connection request', { error }); + return next(error); + } + } + + async getCoreNodeConnectForm (req, res) { + res.render('admin/core-node/connect'); + } + + async getIndex (req, res) { + res.render('admin/core-node/index'); + } +} + +module.exports = async (dtp) => { + let controller = new CoreNodeController(dtp); + return controller; +}; \ No newline at end of file diff --git a/app/controllers/admin/host.js b/app/controllers/admin/host.js index f6a945a6..1322eda3 100644 --- a/app/controllers/admin/host.js +++ b/app/controllers/admin/host.js @@ -1,5 +1,5 @@ // admin/host.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/admin/job-queue.js b/app/controllers/admin/job-queue.js index bef0df6f..69f00f9c 100644 --- a/app/controllers/admin/job-queue.js +++ b/app/controllers/admin/job-queue.js @@ -1,13 +1,13 @@ // admin/job-queue.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 +// Copyright (C) 2022 DTP Technologies, LLC +// All Rights Reserved 'use strict'; const DTP_COMPONENT_NAME = 'admin:job-queue'; const express = require('express'); -const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib'); +const { SiteController, SiteError } = require('../../../lib/site-lib'); class JobQueueController extends SiteController { @@ -24,7 +24,11 @@ class JobQueueController extends SiteController { }); router.param('jobQueueName', this.populateJobQueueName.bind(this)); + router.param('jobId', this.populateJob.bind(this)); + router.post('/:jobQueueName/:jobId/action', this.postJobAction.bind(this)); + + router.get('/:jobQueueName/:jobId', this.getJobView.bind(this)); router.get('/:jobQueueName', this.getJobQueueView.bind(this)); router.get('/', this.getHomeView.bind(this)); @@ -36,6 +40,9 @@ class JobQueueController extends SiteController { try { res.locals.queueName = jobQueueName; res.locals.queue = await jobQueueService.getJobQueue(jobQueueName); + if (!res.locals.queue) { + throw new SiteError(404, 'Job queue not found'); + } return next(); } catch (error) { this.log.error('failed to populate job queue', { jobQueueName, error }); @@ -43,6 +50,46 @@ class JobQueueController extends SiteController { } } + async populateJob (req, res, next, jobId) { + try { + res.locals.job = await res.locals.queue.getJob(jobId); + if (!res.locals.job) { + throw new SiteError(404, 'Job not found'); + } + return next(); + } catch (error) { + this.log.error('failed to populate job', { jobId, error }); + return next(error); + } + } + + async postJobAction (req, res) { + try { + await res.locals.job[req.body.action](); + res.status(200).json({ success: true }); + } catch (error) { + this.log.error('failed to execute job action', { + jobId: res.locals.job.id, + action: req.body.action, + error, + }); + res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } + + async getJobView (req, res, next) { + try { + res.locals.jobLogs = await res.locals.queue.getJobLogs(res.locals.job.id); + res.render('admin/job-queue/job-view'); + } catch (error) { + this.log.error('failed to render job view', { error }); + return next(error); + } + } + async getJobQueueView (req, res, next) { try { res.locals.jobCounts = await res.locals.queue.getJobCounts(); @@ -62,7 +109,8 @@ class JobQueueController extends SiteController { async getHomeView (req, res, next) { const { jobQueue: jobQueueService } = this.dtp.services; try { - res.locals.queues = await jobQueueService.discoverJobQueues('soapy:*:id'); + const prefix = process.env.REDIS_KEY_PREFIX || 'dtp'; + res.locals.queues = await jobQueueService.discoverJobQueues(`${prefix}:*:id`); res.render('admin/job-queue/index'); } catch (error) { this.log.error('failed to populate job queues view', { error }); diff --git a/app/controllers/admin/log.js b/app/controllers/admin/log.js index a6c9e561..f1cfc627 100644 --- a/app/controllers/admin/log.js +++ b/app/controllers/admin/log.js @@ -1,5 +1,5 @@ // admin/log.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/admin/newsletter.js b/app/controllers/admin/newsletter.js deleted file mode 100644 index 78a8a050..00000000 --- a/app/controllers/admin/newsletter.js +++ /dev/null @@ -1,123 +0,0 @@ -// 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, SiteError } = 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.post('/:newsletterId', this.postUpdateNewsletter.bind(this)); - router.post('/', this.postCreateNewsletter.bind(this)); - - router.get('/compose', this.getComposer.bind(this)); - router.get('/:newsletterId', this.getComposer.bind(this)); - - router.get('/', this.getIndex.bind(this)); - - router.delete('/:newsletterId', this.deleteNewsletter.bind(this)); - - return router; - } - - async populateNewsletterId (req, res, next, newsletterId) { - const { newsletter: newsletterService } = this.dtp.services; - try { - res.locals.newsletter = await newsletterService.getById(newsletterId); - if (!res.locals.newsletter) { - throw new SiteError(404, 'Newsletter not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate newsletterId', { newsletterId, error }); - return next(error); - } - } - - async postUpdateNewsletter (req, res, next) { - const { newsletter: newsletterService } = this.dtp.services; - try { - await newsletterService.update(res.locals.newsletter, req.body); - res.redirect('/admin/newsletter'); - } catch (error) { - this.log.error('failed to update newsletter', { newletterId: res.locals.newsletter._id, error }); - return next(error); - } - } - - async postCreateNewsletter (req, res, next) { - const { newsletter: newsletterService } = this.dtp.services; - try { - await newsletterService.create(req.user, req.body); - res.redirect('/admin/newsletter'); - } catch (error) { - this.log.error('failed to create newsletter', { 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, ['draft', 'published']); - res.render('admin/newsletter/index'); - } catch (error) { - return next(error); - } - } - - async deleteNewsletter (req, res) { - const { newsletter: newsletterService } = this.dtp.services; - try { - const displayList = this.createDisplayList('delete-newsletter'); - - await newsletterService.deleteNewsletter(res.locals.newsletter); - - displayList.removeElement(`li[data-newsletter-id="${res.locals.newsletter._id}"]`); - displayList.showNotification( - `Newsletter "${res.locals.newsletter.title}" deleted`, - 'success', - 'bottom-center', - 3000, - ); - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to delete newsletter', { - newsletterId: res.local.newsletter._id, - error, - }); - res.status(error.statusCode || 500).json({ - success: false, - message: error.message, - }); - } - } -} - -module.exports = async (dtp) => { - let controller = new NewsletterController(dtp); - return controller; -}; \ No newline at end of file diff --git a/app/controllers/admin/page.js b/app/controllers/admin/page.js deleted file mode 100644 index 69c9299e..00000000 --- a/app/controllers/admin/page.js +++ /dev/null @@ -1,135 +0,0 @@ -// 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, SiteError } = 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.post('/:pageId', this.pageUpdatePage.bind(this)); - router.post('/', this.pageCreatePage.bind(this)); - - router.get('/compose', this.getComposer.bind(this)); - router.get('/:pageId', this.getComposer.bind(this)); - - router.get('/', this.getIndex.bind(this)); - - router.delete('/:pageId', this.deletePage.bind(this)); - - return router; - } - - async populatePageId (req, res, next, pageId) { - const { page: pageService } = this.dtp.services; - try { - res.locals.page = await pageService.getById(pageId); - if (!res.locals.page) { - throw new SiteError(404, 'Page not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate pageId', { pageId, error }); - return next(error); - } - } - - async pageUpdatePage (req, res, next) { - const { page: pageService } = this.dtp.services; - try { - await pageService.update(res.locals.page, req.body); - res.redirect('/admin/page'); - } catch (error) { - this.log.error('failed to update page', { newletterId: res.locals.page._id, error }); - return next(error); - } - } - - async pageCreatePage (req, res, next) { - const { page: pageService } = this.dtp.services; - try { - await pageService.create(req.user, req.body); - res.redirect('/admin/page'); - } catch (error) { - this.log.error('failed to create page', { error }); - return next(error); - } - } - - async getComposer (req, res, next) { - const { page: pageService } = this.dtp.services; - try { - let excludedPages; - if (res.locals.page) { - excludedPages = [res.locals.page._id]; - } - res.locals.availablePages = await pageService.getAvailablePages(excludedPages); - res.render('admin/page/editor'); - } catch (error) { - this.log.error('failed to serve page editor', { error }); - return next(error); - } - } - - async getIndex (req, res, next) { - const { page: pageService } = this.dtp.services; - try { - res.locals.pagination = this.getPaginationParameters(req, 20); - res.locals.pages = await pageService.getPages(res.locals.pagination, ['draft', 'published', 'archived']); - res.render('admin/page/index'); - } catch (error) { - this.log.error('failed to fetch pages', { error }); - return next(error); - } - } - - async deletePage (req, res) { - const { page: pageService } = this.dtp.services; - try { - const displayList = this.createDisplayList('delete-page'); - - await pageService.deletePage(res.locals.page); - - displayList.removeElement(`li[data-page-id="${res.locals.page._id}"]`); - displayList.showNotification( - `Page "${res.locals.page.title}" deleted`, - 'success', - 'bottom-center', - 3000, - ); - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to delete page', { - pageId: res.local.page._id, - error, - }); - res.status(error.statusCode || 500).json({ - success: false, - message: error.message, - }); - } - } -} - -module.exports = async (dtp) => { - let controller = new PageController(dtp); - return controller; -}; \ No newline at end of file diff --git a/app/controllers/admin/post.js b/app/controllers/admin/post.js deleted file mode 100644 index 499271ed..00000000 --- a/app/controllers/admin/post.js +++ /dev/null @@ -1,152 +0,0 @@ -// 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 multer = require('multer'); - -const { SiteController, SiteError } = require('../../../lib/site-lib'); - -class PostController extends SiteController { - - constructor (dtp) { - super(dtp, DTP_COMPONENT_NAME); - } - - async start ( ) { - const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/uploads/${DTP_COMPONENT_NAME}` }); - - 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.post('/:postId/image', upload.single('imageFile'), this.postUpdateImage.bind(this)); - router.post('/:postId', this.postUpdatePost.bind(this)); - router.post('/', this.postCreatePost.bind(this)); - - router.get('/compose', this.getComposer.bind(this)); - router.get('/:postId', this.getComposer.bind(this)); - - router.get('/', this.getIndex.bind(this)); - - router.delete('/:postId', this.deletePost.bind(this)); - - return router; - } - - async populatePostId (req, res, next, postId) { - const { post: postService } = this.dtp.services; - try { - res.locals.post = await postService.getById(postId); - if (!res.locals.post) { - throw new SiteError(404, 'Post not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate postId', { postId, error }); - return next(error); - } - } - - async postUpdateImage (req, res) { - const { post: postService } = this.dtp.services; - try { - const displayList = this.createDisplayList('post-image'); - - await postService.updateImage(req.user, res.locals.post, req.file); - - displayList.showNotification( - 'Profile photo updated successfully.', - 'success', - 'bottom-center', - 2000, - ); - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to update feature image', { error }); - return res.status(error.statusCode || 500).json({ - success: false, - message: error.message, - }); - } - } - - async postUpdatePost (req, res, next) { - const { post: postService } = this.dtp.services; - try { - await postService.update(res.locals.post, req.body); - res.redirect('/admin/post'); - } catch (error) { - this.log.error('failed to update post', { newletterId: res.locals.post._id, error }); - return next(error); - } - } - - async postCreatePost (req, res, next) { - const { post: postService } = this.dtp.services; - try { - await postService.create(req.user, req.body); - res.redirect('/admin/post'); - } catch (error) { - this.log.error('failed to create post', { error }); - return next(error); - } - } - - async getComposer (req, res) { - res.render('admin/post/editor'); - } - - async getIndex (req, res, next) { - const { post: postService } = this.dtp.services; - try { - res.locals.pagination = this.getPaginationParameters(req, 20); - res.locals.posts = await postService.getAllPosts(res.locals.pagination); - res.render('admin/post/index'); - } catch (error) { - this.log.error('failed to fetch posts', { error }); - return next(error); - } - } - - async deletePost (req, res) { - const { post: postService } = this.dtp.services; - try { - const displayList = this.createDisplayList('delete-post'); - - await postService.deletePost(res.locals.post); - - displayList.removeElement(`li[data-post-id="${res.locals.post._id}"]`); - displayList.showNotification( - `Post "${res.locals.post.title}" deleted`, - 'success', - 'bottom-center', - 3000, - ); - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to delete post', { - postId: res.local.post._id, - error, - }); - res.status(error.statusCode || 500).json({ - success: false, - message: error.message, - }); - } - } -} - -module.exports = async (dtp) => { - let controller = new PostController(dtp); - return controller; -}; \ No newline at end of file diff --git a/app/controllers/admin/settings.js b/app/controllers/admin/settings.js index 1f6994f4..94c51e08 100644 --- a/app/controllers/admin/settings.js +++ b/app/controllers/admin/settings.js @@ -1,5 +1,5 @@ // admin/settings.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index 0b88d73f..682247db 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -1,5 +1,5 @@ // admin/user.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 304a0233..a032ec2c 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -1,5 +1,5 @@ // auth.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/comment.js b/app/controllers/comment.js deleted file mode 100644 index 980e010a..00000000 --- a/app/controllers/comment.js +++ /dev/null @@ -1,158 +0,0 @@ -// comment.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'use strict'; - -const DTP_COMPONENT_NAME = 'comment'; - -const express = require('express'); -const numeral = require('numeral'); - -const { SiteController, SiteError } = require('../../lib/site-lib'); - -class CommentController extends SiteController { - - constructor (dtp) { - super(dtp, DTP_COMPONENT_NAME); - } - - async start ( ) { - const { dtp } = this; - const { limiter: limiterService, session: sessionService } = dtp.services; - - const authRequired = sessionService.authCheckMiddleware({ requiredLogin: true }); - - const router = express.Router(); - dtp.app.use('/comment', router); - - router.use(async (req, res, next) => { - res.locals.currentView = DTP_COMPONENT_NAME; - return next(); - }); - - router.param('commentId', this.populateCommentId.bind(this)); - - router.post('/:commentId/vote', authRequired, this.postVote.bind(this)); - - router.get('/:commentId/replies', this.getCommentReplies.bind(this)); - - router.delete('/:commentId', - authRequired, - limiterService.create(limiterService.config.comment.deleteComment), - this.deleteComment.bind(this), - ); - } - - async populateCommentId (req, res, next, commentId) { - const { comment: commentService } = this.dtp.services; - try { - res.locals.comment = await commentService.getById(commentId); - if (!res.locals.comment) { - return next(new SiteError(404, 'Comment not found')); - } - res.locals.post = res.locals.comment.resource; - return next(); - } catch (error) { - this.log.error('failed to populate commentId', { commentId, error }); - return next(error); - } - } - - async postVote (req, res) { - const { contentVote: contentVoteService } = this.dtp.services; - try { - const displayList = this.createDisplayList('comment-vote'); - const { message, stats } = await contentVoteService.recordVote(req.user, 'Comment', res.locals.comment, req.body.vote); - displayList.setTextContent( - `button[data-comment-id="${res.locals.comment._id}"][data-vote="up"] span.dtp-item-value`, - numeral(stats.upvoteCount).format(stats.upvoteCount > 1000 ? '0,0.0a' : '0,0'), - ); - displayList.setTextContent( - `button[data-comment-id="${res.locals.comment._id}"][data-vote="down"] span.dtp-item-value`, - numeral(stats.downvoteCount).format(stats.upvoteCount > 1000 ? '0,0.0a' : '0,0'), - ); - displayList.showNotification(message, 'success', 'bottom-center', 3000); - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to process comment vote', { error }); - return res.status(error.statusCode || 500).json({ - success: false, - message: error.message, - }); - } - } - - async getCommentReplies (req, res) { - const { comment: commentService } = this.dtp.services; - try { - const displayList = this.createDisplayList('get-replies'); - - if (req.query.buttonId) { - displayList.removeElement(`li.dtp-load-more[data-button-id="${req.query.buttonId}"]`); - } - - Object.assign(res.locals, req.app.locals); - - res.locals.countPerPage = parseInt(req.query.cpp || "20", 10); - if (res.locals.countPerPage < 1) { - res.locals.countPerPage = 1; - } - if (res.locals.countPerPage > 20) { - res.locals.countPerPage = 20; - } - - res.locals.pagination = this.getPaginationParameters(req, res.locals.countPerPage); - res.locals.comments = await commentService.getReplies(res.locals.comment, res.locals.pagination); - - const html = await commentService.renderTemplate('replyList', res.locals); - - const replyList = `ul.dtp-reply-list[data-comment-id="${res.locals.comment._id}"]`; - displayList.addElement(replyList, 'beforeEnd', html); - - const replyListContainer = `.dtp-reply-list-container[data-comment-id="${res.locals.comment._id}"]`; - displayList.removeAttribute(replyListContainer, 'hidden'); - - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to display comment replies', { error }); - res.status(error.statusCode || 500).json({ success: false, message: error.message }); - } - } - - async deleteComment (req, res) { - const { comment: commentService } = this.dtp.services; - try { - const displayList = this.createDisplayList('add-recipient'); - - await commentService.remove(res.locals.comment, 'removed'); - - let selector = `article[data-comment-id="${res.locals.comment._id}"] .comment-content`; - displayList.setTextContent(selector, 'Comment removed'); - - displayList.showNotification( - 'Comment removed successfully', - 'success', - 'bottom-center', - 5000, - ); - - res.status(200).json({ success: true, displayList }); - } catch (error) { - this.log.error('failed to remove comment', { error }); - return res.status(error.statusCode || 500).json({ - success: false, - message: error.message - }); - } - } -} - -module.exports = { - slug: 'comment', - name: 'comment', - create: async (dtp) => { - let controller = new CommentController(dtp); - return controller; - }, -}; \ No newline at end of file diff --git a/app/controllers/email.js b/app/controllers/email.js new file mode 100644 index 00000000..e152299f --- /dev/null +++ b/app/controllers/email.js @@ -0,0 +1,78 @@ +// email.js +// Copyright (C) 2021 Digital Telepresence, LLC +// License: Apache-2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'email'; + +const express = require('express'); + +const { SiteController/*, SiteError*/ } = require('../../lib/site-lib'); + +class EmailController extends SiteController { + + constructor (dtp) { + super(dtp, DTP_COMPONENT_NAME); + } + + async start ( ) { + const { jobQueue: jobQueueService, limiter: limiterService } = this.dtp.services; + + this.emailJobQueue = jobQueueService.getJobQueue('email', { + attempts: 3 + }); + + const router = express.Router(); + this.dtp.app.use('/email', router); + + router.get( + '/verify', + limiterService.create(limiterService.config.email.getEmailVerify), + this.getEmailVerify.bind(this), + ); + + router.get( + '/opt-out', + limiterService.create(limiterService.config.email.getEmailOptOut), + this.getEmailOptOut.bind(this), + ); + + return router; + } + + async getEmailOptOut (req, res, next) { + const { user: userService } = this.dtp.services; + try { + await userService.emailOptOut(req.query.u, req.query.c); + res.render('email/opt-out-success'); + } catch (error) { + this.log.error('failed to opt-out from email', { + userId: req.query.t, + category: req.query.c, + error, + }); + return next(error); + } + } + + async getEmailVerify (req, res, next) { + const { email: emailService } = this.dtp.services; + try { + await emailService.verifyToken(req.query.t); + res.render('email/verify-success'); + } catch (error) { + this.log.error('failed to verify email', { error }); + return next(error); + } + } +} + +module.exports = { + slug: 'email', + name: 'email', + create: async (dtp) => { + let controller = new EmailController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/hive.js b/app/controllers/hive.js new file mode 100644 index 00000000..f0199b2b --- /dev/null +++ b/app/controllers/hive.js @@ -0,0 +1,64 @@ +// hive.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'hive'; + +const path = require('path'); +const express = require('express'); + +const { SiteController } = require('../../lib/site-lib'); + +class HiveController extends SiteController { + + constructor (dtp) { + super(dtp, DTP_COMPONENT_NAME); + this.services = [ ]; + } + + async start ( ) { + const router = express.Router(); + this.dtp.app.use('/hive', router); + + router.use( + async (req, res, next) => { + res.locals.currentView = 'hive'; + res.locals.hiveView = 'home'; + + /* + * TODO: H1V3 authentication before processing request (HTTP Bearer token) + */ + + return next(); + }, + ); + + router.use('/kaleidoscope',await this.loadChild(path.join(__dirname, 'hive', 'kaleidoscope'))); + this.services.push({ name: 'kaleidoscope', url: '/hive/kaleidoscope' }); + + router.get('/', this.getHiveRoot.bind(this)); + + return router; + } + + async getHiveRoot (req, res) { + res.status(200).json({ + component: DTP_COMPONENT_NAME, + host: this.dtp.pkg.name, + description: this.dtp.pkg.description, + version: this.dtp.pkg.version, + services: this.services, + }); + } +} + +module.exports = { + slug: 'hive', + name: 'hive', + create: async (dtp) => { + let controller = new HiveController(dtp); + return controller; + }, +}; \ No newline at end of file diff --git a/app/controllers/hive/kaleidoscope.js b/app/controllers/hive/kaleidoscope.js new file mode 100644 index 00000000..cbfe9f2b --- /dev/null +++ b/app/controllers/hive/kaleidoscope.js @@ -0,0 +1,72 @@ +// hive/kaleidoscope.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'hive:kaleidoscope'; + +const express = require('express'); + +const { SiteController } = require('../../../lib/site-lib'); + +class HostController extends SiteController { + + constructor (dtp) { + super(dtp, DTP_COMPONENT_NAME); + + this.methods = [ + { + name: 'postEvent', + url: '/kaleidoscope/event', + method: 'POST', + }, + ]; + } + + async start ( ) { + const router = express.Router(); + router.use(async (req, res, next) => { + res.locals.currentView = 'hive'; + res.locals.hiveView = 'kaleidoscope'; + return next(); + }); + + router.post('/core-node/connect', this.postCoreNodeConnect.bind(this)); + router.post('/event', this.postEvent.bind(this)); + + router.get('/', this.getKaleidoscopeRoot.bind(this)); + + return router; + } + + async postCoreNodeConnect (req, res, next) { + const { coreNode: coreNodeService } = this.dtp.services; + try { + await coreNodeService.connect(req.body); + } catch (error) { + this.log.error('failed to create Core Node connection', { error }); + return next(error); + } + } + + async postEvent (req, res) { + this.log.debug('kaleidoscope event received', { event: req.body.event }); + this.emit('kaleidoscope:event', req, res); + res.status(200).json({ success: true }); + } + + async getKaleidoscopeRoot (req, res) { + res.status(200).json({ + component: DTP_COMPONENT_NAME, + version: this.dtp.pkg.version, + services: this.services, + methods: this.methods, + }); + } +} + +module.exports = async (dtp) => { + let controller = new HostController(dtp); + return controller; +}; diff --git a/app/controllers/home.js b/app/controllers/home.js index 594243bc..3bb79f80 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -1,5 +1,5 @@ // home.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -65,17 +65,8 @@ class HomeController extends SiteController { res.render('policy/view'); } - async getHome (req, res, next) { - const { post: postService } = this.dtp.services; - try { - res.locals.pagination = this.getPaginationParameters(req, 20); - res.locals.featuredPosts = await postService.getFeaturedPosts(3); - res.locals.posts = await postService.getPosts(res.locals.pagination); - - res.render('index'); - } catch (error) { - return next(error); - } + async getHome (req, res) { + res.render('index'); } } @@ -87,4 +78,4 @@ module.exports = { let controller = new HomeController(dtp); return controller; }, -}; +}; \ No newline at end of file diff --git a/app/controllers/image.js b/app/controllers/image.js index 4cd3a65a..bafdb683 100644 --- a/app/controllers/image.js +++ b/app/controllers/image.js @@ -1,5 +1,5 @@ // page.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/controllers/manifest.js b/app/controllers/manifest.js index cbd83e5c..c9edf1c5 100644 --- a/app/controllers/manifest.js +++ b/app/controllers/manifest.js @@ -1,5 +1,5 @@ // manifest.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -52,7 +52,7 @@ class ManifestController extends SiteController { [512, 384, 256, 192, 144, 96, 72, 48, 32, 16].forEach((size) => { manifest.icons.push({ - src: `/img/icon/icon-${size}x${size}.png`, + src: `/img/icon/${this.dtp.config.site.domainKey}/icon-${size}x${size}.png`, sizes: `${size}x${size}`, type: 'image/png' }); diff --git a/app/controllers/newsletter.js b/app/controllers/newsletter.js deleted file mode 100644 index 99247041..00000000 --- a/app/controllers/newsletter.js +++ /dev/null @@ -1,104 +0,0 @@ -// 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/${this.dtp.config.site.domainKey}/uploads/${DTP_COMPONENT_NAME}` }); - - 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 } = this.dtp.services; - try { - const displayList = this.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 = { - slug: 'newsletter', - name: 'newsletter', - create: async (dtp) => { - let controller = new NewsletterController(dtp); - return controller; - }, -}; diff --git a/app/controllers/user.js b/app/controllers/user.js index cd3494c4..a71ede5f 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,5 +1,5 @@ // user.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -31,6 +31,12 @@ class UserController extends SiteController { dtp.app.use('/user', router); const authRequired = sessionService.authCheckMiddleware({ requireLogin: true }); + + const otpSetup = otpAuthService.middleware('Account', { + adminRequired: false, + otpRequired: true, + otpRedirectURL: async (req) => { return `/user/${req.user._id}`; }, + }); const otpMiddleware = otpAuthService.middleware('Account', { adminRequired: false, otpRequired: false, @@ -58,31 +64,52 @@ class UserController extends SiteController { router.param('userId', this.populateUser.bind(this)); - router.post('/:userId/profile-photo', + router.post( + '/:userId/profile-photo', limiterService.create(limiterService.config.user.postProfilePhoto), + checkProfileOwner, upload.single('imageFile'), this.postProfilePhoto.bind(this), ); - router.post('/:userId/settings', + router.post( + '/:userId/settings', limiterService.create(limiterService.config.user.postUpdateSettings), + checkProfileOwner, upload.none(), this.postUpdateSettings.bind(this), ); - router.post('/', + router.post( + '/', limiterService.create(limiterService.config.user.postCreate), this.postCreateUser.bind(this), ); - router.get('/:userId/settings', + router.get( + '/:userId/otp-setup', + limiterService.create(limiterService.config.user.getOtpSetup), + otpSetup, + this.getOtpSetup.bind(this), + ); + router.get( + '/:userId/otp-disable', + limiterService.create(limiterService.config.user.getOtpDisable), + authRequired, + this.getOtpDisable.bind(this), + ); + + + router.get( + '/:userId/settings', limiterService.create(limiterService.config.user.getSettings), authRequired, otpMiddleware, checkProfileOwner, this.getUserSettingsView.bind(this), ); - router.get('/:userId', + router.get( + '/:userId', limiterService.create(limiterService.config.user.getUserProfile), authRequired, otpMiddleware, @@ -90,7 +117,8 @@ class UserController extends SiteController { this.getUserView.bind(this), ); - router.delete('/:userId/profile-photo', + router.delete( + '/:userId/profile-photo', limiterService.create(limiterService.config.user.deleteProfilePhoto), authRequired, checkProfileOwner, @@ -198,12 +226,7 @@ class UserController extends SiteController { await userService.updateSettings(req.user, req.body); - displayList.showNotification( - 'Member account settings updated successfully.', - 'success', - 'bottom-center', - 6000, - ); + displayList.reload(); res.status(200).json({ success: true, displayList }); } catch (error) { this.log.error('failed to update account settings', { error }); @@ -214,8 +237,29 @@ class UserController extends SiteController { } } + async getOtpSetup (req, res) { + res.render('user/otp-setup-complete'); + } + + async getOtpDisable (req, res) { + const { otpAuth: otpAuthService } = this.dtp.services; + try { + await otpAuthService.destroyOtpSession(req, 'Account'); + await otpAuthService.removeForUser(req.user, 'Account'); + res.render('user/otp-disabled'); + } catch (error) { + this.log.error('failed to disable OTP service for Account', { error }); + res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } + async getUserSettingsView (req, res, next) { + const { otpAuth: otpAuthService } = this.dtp.services; try { + res.locals.hasOtpAccount = await otpAuthService.isUserProtected(req.user, 'Account'); res.locals.startTab = req.query.st || 'watch'; res.render('user/settings'); } catch (error) { diff --git a/app/controllers/welcome.js b/app/controllers/welcome.js index b77acb40..763456f1 100644 --- a/app/controllers/welcome.js +++ b/app/controllers/welcome.js @@ -1,5 +1,5 @@ // welcome.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/category.js b/app/models/category.js deleted file mode 100644 index 33cba637..00000000 --- a/app/models/category.js +++ /dev/null @@ -1,25 +0,0 @@ -// category.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'use strict'; - -const mongoose = require('mongoose'); - -const Schema = mongoose.Schema; - -const CategorySchema = new Schema({ - name: { type: String }, - slug: { type: String, lowercase: true, required: true, index: 1 }, - description: { type: String }, - images: { - header: { type: Schema.ObjectId }, - icon: { type: Schema.ObjectId }, - }, - stats: { - articleCount: { type: Number, default: 0, required: true }, - articleViewCount: { type: Number, default: 0, required: true }, - }, -}); - -module.exports = mongoose.model('Category', CategorySchema); \ No newline at end of file diff --git a/app/models/chat-message.js b/app/models/chat-message.js index c0c3d54c..2a3f47b0 100644 --- a/app/models/chat-message.js +++ b/app/models/chat-message.js @@ -1,5 +1,5 @@ // chat-message.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License Apache-2.0 'use strict'; diff --git a/app/models/comment.js b/app/models/comment.js index bce34200..d9d93e59 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -1,5 +1,5 @@ // comment.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/connect-token.js b/app/models/connect-token.js index 12096cd3..3a1b3b3e 100644 --- a/app/models/connect-token.js +++ b/app/models/connect-token.js @@ -1,5 +1,5 @@ // connect-token.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/content-report.js b/app/models/content-report.js index ac733863..d14879a9 100644 --- a/app/models/content-report.js +++ b/app/models/content-report.js @@ -1,5 +1,5 @@ // content-report.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/content-vote.js b/app/models/content-vote.js index 0cbe24ab..490b0981 100644 --- a/app/models/content-vote.js +++ b/app/models/content-vote.js @@ -1,5 +1,5 @@ // content-vote.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/core-node-request.js b/app/models/core-node-request.js new file mode 100644 index 00000000..dde2409b --- /dev/null +++ b/app/models/core-node-request.js @@ -0,0 +1,37 @@ +// core-node-request.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +/* + * Used for authenticating responses received and gathering performance and use + * metrics for communications with Cores. + * + * When a request is created, an authentication token is generated and + * information about the request is stored. This also provides the request ID. + * + * When a resonse is received for a request, this record is fetched. The token + * claimed status and value are checked. Information about the response is + * recorded, and request execution time information is recorded. + */ + +const CoreNodeRequestSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: 1 }, + core: { type: Schema.ObjectId, required: true, ref: 'CoreNode' }, + token: { + value: { type: String, required: true }, + claimed: { type: Boolean, default: false, required: true }, + }, + url: { type: String }, + response: { + received: { type: Date }, + elapsed: { type: Number }, + isError: { type: Boolean, default: false }, + }, +}); + +module.exports = mongoose.model('CoreNodeRequest', CoreNodeRequestSchema); \ No newline at end of file diff --git a/app/models/core-node.js b/app/models/core-node.js new file mode 100644 index 00000000..f8376830 --- /dev/null +++ b/app/models/core-node.js @@ -0,0 +1,18 @@ +// core-node.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const CoreNodeSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: 1 }, + address: { + host: { type: String, required: true }, + port: { type: Number, min: 1, max: 65535, required: true }, + }, +}); + +module.exports = mongoose.model('CoreNode', CoreNodeSchema); \ No newline at end of file diff --git a/app/models/csrf-token.js b/app/models/csrf-token.js index 6969ab3e..81d75dfb 100644 --- a/app/models/csrf-token.js +++ b/app/models/csrf-token.js @@ -1,5 +1,5 @@ // csrf-token.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/email-blacklist.js b/app/models/email-blacklist.js index 230c8af5..ff03a3a7 100644 --- a/app/models/email-blacklist.js +++ b/app/models/email-blacklist.js @@ -1,5 +1,5 @@ // email-blacklist.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/email-body.js b/app/models/email-body.js index 3b973dfb..e8e38246 100644 --- a/app/models/email-body.js +++ b/app/models/email-body.js @@ -1,5 +1,5 @@ // email-body.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/email-log.js b/app/models/email-log.js new file mode 100644 index 00000000..2131e25f --- /dev/null +++ b/app/models/email-log.js @@ -0,0 +1,19 @@ +// email-log.js +// Copyright (C) 2022 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const EmailLogSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1 }, + from: { type: String, required: true, }, + to: { type: String, required: true }, + to_lc: { type: String, required: true, lowercase: true, index: 1 }, + subject: { type: String }, + messageId: { type: String }, +}); + +module.exports = mongoose.model('EmailLog', EmailLogSchema); \ No newline at end of file diff --git a/app/models/email-verify.js b/app/models/email-verify.js new file mode 100644 index 00000000..9b869006 --- /dev/null +++ b/app/models/email-verify.js @@ -0,0 +1,18 @@ +// email-verify.js +// Copyright (C) 2021 Digital Telepresence, LLC +// License: Apache-2.0 + +'use strict'; + +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const EmailVerifySchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1, expires: '30d' }, + verified: { type: Date }, + user: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, + token: { type: String, required: true }, +}); + +module.exports = mongoose.model('EmailVerify', EmailVerifySchema); \ No newline at end of file diff --git a/app/models/email.js b/app/models/email.js index 6b8b446e..f76bcae0 100644 --- a/app/models/email.js +++ b/app/models/email.js @@ -1,5 +1,5 @@ // email.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/image.js b/app/models/image.js index 4ffe5909..d31e6da9 100644 --- a/app/models/image.js +++ b/app/models/image.js @@ -1,5 +1,5 @@ // image.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/lib/geo-types.js b/app/models/lib/geo-types.js index dd5cdce7..4c09c830 100644 --- a/app/models/lib/geo-types.js +++ b/app/models/lib/geo-types.js @@ -1,5 +1,5 @@ // geo-types.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/lib/resource-stats.js b/app/models/lib/resource-stats.js index 6d4dce82..016644df 100644 --- a/app/models/lib/resource-stats.js +++ b/app/models/lib/resource-stats.js @@ -1,5 +1,5 @@ // lib/resource-stats.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/log.js b/app/models/log.js index b2220d72..8b25f15d 100644 --- a/app/models/log.js +++ b/app/models/log.js @@ -1,5 +1,5 @@ // log.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/net-host-stats.js b/app/models/net-host-stats.js index fb295f81..084068e7 100644 --- a/app/models/net-host-stats.js +++ b/app/models/net-host-stats.js @@ -1,5 +1,5 @@ // net-host-stats.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/net-host.js b/app/models/net-host.js index 51144931..0646b2e3 100644 --- a/app/models/net-host.js +++ b/app/models/net-host.js @@ -1,5 +1,5 @@ // net-host.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/newsletter-recipient.js b/app/models/newsletter-recipient.js deleted file mode 100644 index d4340291..00000000 --- a/app/models/newsletter-recipient.js +++ /dev/null @@ -1,21 +0,0 @@ -// 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); \ No newline at end of file diff --git a/app/models/newsletter.js b/app/models/newsletter.js deleted file mode 100644 index f1e8bdd2..00000000 --- a/app/models/newsletter.js +++ /dev/null @@ -1,31 +0,0 @@ -// 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); \ No newline at end of file diff --git a/app/models/otp-account.js b/app/models/otp-account.js index 1b94a4bb..fcaea752 100644 --- a/app/models/otp-account.js +++ b/app/models/otp-account.js @@ -1,5 +1,5 @@ // otp-account.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/page.js b/app/models/page.js deleted file mode 100644 index 705fb822..00000000 --- a/app/models/page.js +++ /dev/null @@ -1,29 +0,0 @@ -// page.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'use strict'; - -const mongoose = require('mongoose'); -const Schema = mongoose.Schema; - -const PAGE_STATUS_LIST = ['draft','published','archived']; - -const PageSchema = new Schema({ - title: { type: String, required: true }, - slug: { type: String, required: true, lowercase: true, unique: true }, - image: { - header: { type: Schema.ObjectId, ref: 'Image' }, - icon: { type: Schema.ObjectId, ref: 'Image' }, - }, - content: { type: String, required: true, select: false }, - status: { type: String, enum: PAGE_STATUS_LIST, default: 'draft', index: true }, - menu: { - icon: { type: String, required: true }, - label: { type: String, required: true }, - order: { type: Number, default: 0, required: true }, - parent: { type: Schema.ObjectId, index: 1, ref: 'Page' }, - }, -}); - -module.exports = mongoose.model('Page', PageSchema); \ No newline at end of file diff --git a/app/models/post.js b/app/models/post.js deleted file mode 100644 index 750ec457..00000000 --- a/app/models/post.js +++ /dev/null @@ -1,36 +0,0 @@ -// post.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'use strict'; - -const path = require('path'); -const mongoose = require('mongoose'); - -const Schema = mongoose.Schema; - -const { - ResourceStats, - ResourceStatsDefaults, -} = require(path.join(__dirname, 'lib', 'resource-stats.js')); - -const POST_STATUS_LIST = ['draft','published','archived']; - -const PostSchema = new Schema({ - created: { type: Date, default: Date.now, required: true, index: -1 }, - updated: { type: Date }, - author: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, - image: { type: Schema.ObjectId, ref: 'Image' }, - title: { type: String, required: true }, - slug: { type: String, required: true, lowercase: true, unique: true }, - summary: { type: String, required: true }, - content: { type: String, required: true, select: false }, - status: { type: String, enum: POST_STATUS_LIST, default: 'draft', index: true }, - stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true }, - flags: { - enableComments: { type: Boolean, default: true, index: true }, - isFeatured: { type: Boolean, default: false, index: true }, - }, -}); - -module.exports = mongoose.model('Post', PostSchema); \ No newline at end of file diff --git a/app/models/resource-view.js b/app/models/resource-view.js index 1f9f4473..ec002689 100644 --- a/app/models/resource-view.js +++ b/app/models/resource-view.js @@ -1,5 +1,5 @@ // resource-view.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/resource-visit.js b/app/models/resource-visit.js index f0bbcc99..770ce64a 100644 --- a/app/models/resource-visit.js +++ b/app/models/resource-visit.js @@ -1,5 +1,5 @@ // resource-visit.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/user-block.js b/app/models/user-block.js index 31e65052..20462677 100644 --- a/app/models/user-block.js +++ b/app/models/user-block.js @@ -1,5 +1,5 @@ // user-block.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/user-notification.js b/app/models/user-notification.js index 6d73366b..5e38510d 100644 --- a/app/models/user-notification.js +++ b/app/models/user-notification.js @@ -1,5 +1,5 @@ // user-notification.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/models/user.js b/app/models/user.js index a63dd691..14d681b3 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -1,5 +1,5 @@ // user.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -10,6 +10,8 @@ const Schema = mongoose.Schema; const { ResourceStats, ResourceStatsDefaults } = require('./lib/resource-stats'); +const DTP_THEME_LIST = ['dtp-light', 'dtp-dark']; + const UserFlagsSchema = new Schema({ isAdmin: { type: Boolean, default: false, required: true }, isModerator: { type: Boolean, default: false, required: true }, @@ -20,8 +22,11 @@ const UserPermissionsSchema = new Schema({ canChat: { type: Boolean, default: true, required: true }, canComment: { type: Boolean, default: true, required: true }, canReport: { type: Boolean, default: true, required: true }, - canAuthorPages: { type: Boolean, default: false, required: true }, - canAuthorPosts: { type: Boolean, default: false, required: true }, +}); + +const UserOptInSchema = new Schema({ + system: { type: Boolean, default: true, required: true }, + marketing: { type: Boolean, default: true, required: true }, }); const UserSchema = new Schema({ @@ -32,14 +37,15 @@ const UserSchema = new Schema({ passwordSalt: { type: String, required: true }, password: { type: String, required: true }, displayName: { type: String }, - bio: { type: String, maxlength: 300 }, picture: { large: { type: Schema.ObjectId, ref: 'Image' }, small: { type: Schema.ObjectId, ref: 'Image' }, }, flags: { type: UserFlagsSchema, select: false }, permissions: { type: UserPermissionsSchema, select: false }, + optIn: { type: UserOptInSchema, required: true, select: false }, + theme: { type: String, enum: DTP_THEME_LIST, default: 'dtp-light', required: true }, stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true }, }); -module.exports = mongoose.model('User', UserSchema); +module.exports = mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/app/services/cache.js b/app/services/cache.js index 8e229865..50186f2d 100644 --- a/app/services/cache.js +++ b/app/services/cache.js @@ -1,5 +1,5 @@ // cache.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/chat.js b/app/services/chat.js index facb404f..1734766a 100644 --- a/app/services/chat.js +++ b/app/services/chat.js @@ -1,5 +1,5 @@ // chat.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/comment.js b/app/services/comment.js index 4189160c..da0cc85a 100644 --- a/app/services/comment.js +++ b/app/services/comment.js @@ -1,5 +1,5 @@ // comment.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/content-report.js b/app/services/content-report.js index ef432b52..b94c06b8 100644 --- a/app/services/content-report.js +++ b/app/services/content-report.js @@ -1,5 +1,5 @@ // content-report.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/content-vote.js b/app/services/content-vote.js index e4413656..df6948ef 100644 --- a/app/services/content-vote.js +++ b/app/services/content-vote.js @@ -1,5 +1,5 @@ // content-vote.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/core-node.js b/app/services/core-node.js new file mode 100644 index 00000000..6ab593e5 --- /dev/null +++ b/app/services/core-node.js @@ -0,0 +1,97 @@ +// core-node.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const uuidv4 = require('uuid').v4; + +const mongoose = require('mongoose'); +const fetch = require('node-fetch'); // jshint ignore:line + +const CoreNode = mongoose.model('CoreNode'); +const CoreNodeRequest = mongoose.model('CoreNodeRequest'); + +const { SiteService, SiteError } = require('../../lib/site-lib'); + +class CoreNodeService extends SiteService { + + constructor (dtp) { + super(dtp, module.exports); + } + + async create (coreDefinition) { + const core = new CoreNode(); + core.created = new Date(); + + core.address = { }; + + if (!coreDefinition.host) { + throw new SiteError(406, 'Must provide Core Node host address'); + } + core.address.host = coreDefinition.host.trim(); + + if (!coreDefinition.port) { + throw new SiteError(406, 'Must provide Core Node TCP port number'); + } + + coreDefinition.port = parseInt(coreDefinition.port, 10); + if (coreDefinition.port < 1 || coreDefinition.port > 65535) { + throw new SiteError(406, 'Core Node port number out of range'); + } + + await core.save(); + + return core.toObject(); + } + + async broadcast (request) { + const results = [ ]; + await CoreNode + .find() + .cursor() + .eachAsync(async (core) => { + try { + const response = await this.sendRequest(core, request); + results.push({ coreId: core._id, request, response }); + } catch (error) { + this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error }); + } + }); + return results; + } + + async sendRequest (core, request) { + const requestUrl = `https://${core.address.host}:${core.address.port}${request.url}`; + + const req = new CoreNodeRequest(); + req.created = new Date(); + req.core = core._id; + req.token = { + value: uuidv4(), + claimed: false, + }; + req.url = request.url; + await req.save(); + + try { + const response = await fetch(requestUrl, { + method: request.method, + body: request.body, + }); + const json = await response.json(); + return { request: req.toObject(), response: json }; + } catch (error) { + this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error }); + throw error; + } + + return req.toObject(); + } +} + +module.exports = { + slug: 'core-node', + name: 'coreNode', + create: (dtp) => { return new CoreNodeService(dtp); }, +}; \ No newline at end of file diff --git a/app/services/crypto.js b/app/services/crypto.js index dc89f9e9..8e3568cb 100644 --- a/app/services/crypto.js +++ b/app/services/crypto.js @@ -1,5 +1,5 @@ // crypto.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/csrf-token.js b/app/services/csrf-token.js index 273d513c..08bba509 100644 --- a/app/services/csrf-token.js +++ b/app/services/csrf-token.js @@ -1,5 +1,5 @@ // csrf-token.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/display-engine.js b/app/services/display-engine.js index dcd95c37..3dd76eb4 100644 --- a/app/services/display-engine.js +++ b/app/services/display-engine.js @@ -1,5 +1,5 @@ // display-engine.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -109,6 +109,13 @@ class DisplayList { params: { href }, }); } + + reload ( ) { + this.commands.push({ + action: 'reload', + params: { }, + }); + } } class DisplayEngineService extends SiteService { diff --git a/app/services/email.js b/app/services/email.js index 13adde69..3d88c5dc 100644 --- a/app/services/email.js +++ b/app/services/email.js @@ -1,22 +1,23 @@ // email.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; -const path = require('path'); -const pug = require('pug'); - const nodemailer = require('nodemailer'); +const uuidv4 = require('uuid').v4; const mongoose = require('mongoose'); + const EmailBlacklist = mongoose.model('EmailBlacklist'); +const EmailVerify = mongoose.model('EmailVerify'); +const EmailLog = mongoose.model('EmailLog'); const disposableEmailDomains = require('disposable-email-provider-domains'); const emailValidator = require('email-validator'); const emailDomainCheck = require('email-domain-check'); -const { SiteService } = require('../../lib/site-lib'); +const { SiteService, SiteError } = require('../../lib/site-lib'); class EmailService extends SiteService { @@ -27,7 +28,8 @@ class EmailService extends SiteService { async start ( ) { await super.start(); - if (process.env.DTP_EMAIL_ENABLED !== 'enabled') { + if (process.env.DTP_EMAIL_SERVICE !== 'enabled') { + this.log.info("DTP_EMAIL_SERVICE is disabled, the system can't send email and will not try."); return; } @@ -51,17 +53,35 @@ class EmailService extends SiteService { this.templates = { html: { - welcome: pug.compileFile(path.join(this.dtp.config.root, 'app', 'templates', 'html', 'welcome.pug')), + userEmail: this.loadAppTemplate('html', 'user-email.pug'), + welcome: this.loadAppTemplate('html', 'welcome.pug'), }, text: { - welcome: pug.compileFile(path.join(this.dtp.config.root, 'app', 'templates', 'text', 'welcome.pug')), + userEmail: this.loadAppTemplate('text', 'user-email.pug'), + welcome: this.loadAppTemplate('text', 'welcome.pug'), }, }; } + async renderTemplate (templateId, templateType, templateModel) { + this.log.debug('rendering email template', { templateId, templateType }); + return this.templates[templateType][templateId](templateModel); + } + async send (message) { + const NOW = new Date(); + this.log.info('sending email', { to: message.to, subject: message.subject }); - await this.transport.sendMail(message); + const response = await this.transport.sendMail(message); + + await EmailLog.create({ + created: NOW, + from: message.from, + to: message.to, + to_lc: message.to.toLowerCase(), + subject: message.subject, + messageId: response.messageId, + }); } async checkEmailAddress (emailAddress) { @@ -97,8 +117,52 @@ class EmailService extends SiteService { return false; } - async renderTemplate (templateId, templateType, message) { - return this.templates[templateType][templateId](message); + async createVerificationToken (user) { + const NOW = new Date(); + const verify = new EmailVerify(); + + verify.created = NOW; + verify.user = user._id; + verify.token = uuidv4(); + + await verify.save(); + + this.log.info('created email verification token for user', { user: user._id }); + + return verify.toObject(); + } + + async verifyToken (token) { + const NOW = new Date(); + const { user: userService } = this.dtp.services; + + // fetch the token from the db + const emailVerify = await EmailVerify + .findOne({ token: token }) + .populate(this.populateEmailVerify) + .lean(); + + // verify that the token is at least valid (it exists) + if (!emailVerify) { + this.log.error('email verify token not found', { token }); + throw new SiteError(403, 'Email verification token is invalid'); + } + + // verify that it hasn't already been verified (user clicked link more than once) + if (emailVerify.verified) { + this.log.error('email verify token already claimed', { token }); + throw new SiteError(403, 'Email verification token is invalid'); + } + + this.log.info('marking user email verified', { userId: emailVerify.user._id }); + await userService.setEmailVerification(emailVerify.user, true); + + await EmailVerify.updateOne({ _id: emailVerify._id }, { $set: { verified: NOW } }); + } + + async removeVerificationTokensForUser (user) { + this.log.info('removing all pending email address verification tokens for user', { user: user._id }); + await EmailVerify.deleteMany({ user: user._id }); } } diff --git a/app/services/host-cache.js b/app/services/host-cache.js index b4143e5b..ae60cd47 100644 --- a/app/services/host-cache.js +++ b/app/services/host-cache.js @@ -1,5 +1,5 @@ // host-cache.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/image.js b/app/services/image.js index dbb0f292..a7a99ddb 100644 --- a/app/services/image.js +++ b/app/services/image.js @@ -1,5 +1,5 @@ // minio.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/job-queue.js b/app/services/job-queue.js index 7e7b3233..472a304e 100644 --- a/app/services/job-queue.js +++ b/app/services/job-queue.js @@ -1,5 +1,5 @@ // job-queue.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -46,7 +46,6 @@ class JobQueueService extends SiteService { port: parseInt(process.env.REDIS_PORT || '6379', 10), password: process.env.REDIS_PASSWORD, keyPrefix: process.env.REDIS_KEY_PREFIX, - lazyConnect: true, }, defaultJobOptions, }); diff --git a/app/services/limiter.js b/app/services/limiter.js index b06d05c9..6d512831 100644 --- a/app/services/limiter.js +++ b/app/services/limiter.js @@ -1,5 +1,5 @@ // limiter.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/log.js b/app/services/log.js index f6f5e57f..039b4d07 100644 --- a/app/services/log.js +++ b/app/services/log.js @@ -1,5 +1,5 @@ // log.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/markdown.js b/app/services/markdown.js index ff216103..9e624417 100644 --- a/app/services/markdown.js +++ b/app/services/markdown.js @@ -1,5 +1,5 @@ // markdown.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/media.js b/app/services/media.js index 4d2b72a6..0a589529 100644 --- a/app/services/media.js +++ b/app/services/media.js @@ -1,5 +1,5 @@ // article.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/minio.js b/app/services/minio.js index c5039a6b..6d23a7e1 100644 --- a/app/services/minio.js +++ b/app/services/minio.js @@ -1,5 +1,5 @@ // minio.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/newsletter.js b/app/services/newsletter.js deleted file mode 100644 index bcc4c898..00000000 --- a/app/services/newsletter.js +++ /dev/null @@ -1,123 +0,0 @@ -// 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.html = newsletterDefinition['content.html'].trim(); - newsletter.content.text = striptags(newsletterDefinition['content.text'].trim()); - newsletter.status = 'draft'; - - 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.html']) { - updateOp.$set['content.html'] = newsletterDefinition['content.html'].trim(); - } - if (newsletterDefinition['content.text']) { - updateOp.$set['content.text'] = striptags(newsletterDefinition['content.text'].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.html +content.text') - .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(); - } - - async deleteNewsletter (newsletter) { - this.log.info('deleting newsletter', { newsletterId: newsletter._id }); - await Newsletter.deleteOne({ _id: newsletter._id }); - } -} - -module.exports = { - slug: 'newsletter', - name: 'newsletter', - create: (dtp) => { return new NewsletterService(dtp); }, -}; \ No newline at end of file diff --git a/app/services/oauth2.js b/app/services/oauth2.js new file mode 100644 index 00000000..2f56fc44 --- /dev/null +++ b/app/services/oauth2.js @@ -0,0 +1,191 @@ +// oauth2.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const passport = require('passport'); + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const uuidv4 = require('uuid').v4; +const oauth2orize = require('oauth2orize'); + +const { SiteService/*, SiteError*/ } = require('../../lib/site-lib'); + +class OAuth2Service extends SiteService { + + constructor (dtp) { + super(dtp, module.exports); + } + + async start ( ) { + this.models = { }; + + /* + * OAuth2Client Model + */ + const ClientSchema = new Schema({ + created: { type: Date, default: Date.now, required: true }, + updated: { type: Date, default: Date.now, required: true }, + secret: { type: String, required: true }, + redirectURI: { type: String, required: true }, + }); + this.log.info('registering OAuth2Client model'); + this.models.Client = mongoose.model('OAuth2Client', ClientSchema); + + /* + * OAuth2AuthorizationCode model + */ + const AuthorizationCodeSchema = new Schema({ + code: { type: String, required: true, index: 1 }, + clientId: { type: Schema.ObjectId, required: true, index: 1 }, + redirectURI: { type: String, required: true }, + user: { type: Schema.ObjectId, required: true, index: 1 }, + scope: { type: [String], required: true }, + }); + this.log.info('registering OAuth2AuthorizationCode model'); + this.models.AuthorizationCode = mongoose.model('OAuth2AuthorizationCode', AuthorizationCodeSchema); + + /* + * OAuth2AccessToken model + */ + const AccessTokenSchema = new Schema({ + token: { type: String, required: true, unique: true, index: 1 }, + user: { type: Schema.ObjectId, required: true, index: 1 }, + clientId: { type: Schema.ObjectId, required: true, index: 1 }, + scope: { type: [String], required: true }, + }); + this.log.info('registering OAuth2AccessToken model'); + this.models.AccessToken = mongoose.model('OAuth2AccessToken', AccessTokenSchema); + + /* + * Create OAuth2 server instance + */ + const options = { }; + this.log.info('creating OAuth2 server instance', { options }); + this.server = oauth2orize.createServer(options); + this.server.grant(oauth2orize.grant.code(this.processGrant.bind(this))); + this.server.exchange(oauth2orize.exchange.code(this.processExchange.bind(this))); + + /* + * Register client serialization callbacks + */ + this.log.info('registering OAuth2 client serialization routines'); + this.server.serializeClient(this.serializeClient.bind(this)); + this.server.deserializeClient(this.deserializeClient.bind(this)); + } + + async serializeClient (client, done) { + return done(null, client.id); + } + + async deserializeClient (clientId, done) { + try { + const client = await this.models.Client.findOne({ _id: clientId }).lean(); + return done(null, client); + } catch (error) { + this.log.error('failed to deserialize OAuth2 client', { clientId, error }); + return done(error); + } + } + + attachRoutes (app) { + const { session: sessionService } = this.dtp.services; + + const requireLogin = sessionService.authCheckMiddleware({ requireLogin: true }); + + app.get( + '/dialog/authorize', + requireLogin, + this.server.authorize(this.processAuthorize.bind(this)), + this.renderAuthorizeDialog.bind(this), + ); + + app.post( + '/dialog/authorize/decision', + requireLogin, + this.server.decision(), + ); + + app.post( + '/token', + passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), + this.server.token(), + this.server.errorHandler(), + ); + } + + async renderAuthorizeDialog (req, res) { + res.locals.transactionID = req.oauth2.transactionID; + res.locals.client = req.oauth2.client; + res.render('oauth2/authorize-dialog'); + } + + async processAuthorize (clientID, redirectURI, done) { + try { + const client = await this.models.Clients.findOne({ clientID }); + if (!client) { + return done(null, false); + } + if (client.redirectUri !== redirectURI) { + return done(null, false); + } + return done(null, client, client.redirectURI); + } catch (error) { + this.log.error('failed to process OAuth2 authorize', { error }); + return done(error); + } + } + + async processGrant (client, redirectURI, user, ares, done) { + try { + var code = uuidv4(); + var ac = new this.models.AuthorizationCode({ + code, + clientId: client.id, + redirectURI, + user: user.id, + scope: ares.scope, + }); + await ac.save(); + return done(null, code); + } catch (error) { + this.log.error('failed to process OAuth2 grant', { error }); + return done(error); + } + } + + async processExchange (client, code, redirectURI, done) { + try { + const ac = await this.models.AuthorizationCode.findOne({ code }); + if (client.id !== ac.clientId) { + return done(null, false); + } + if (redirectURI !== ac.redirectUri) { + return done(null, false); + } + + var token = uuidv4(); + var at = new this.models.AccessToken({ + token, + user: ac.userId, + clientId: ac.clientId, + scope: ac.scope, + }); + await at.save(); + + return done(null, token); + } catch (error) { + this.log.error('failed to process OAuth2 exchange', { error }); + return done(error); + } + } +} + +module.exports = { + slug: 'oauth2', + name: 'oauth2', + create: (dtp) => { return new OAuth2Service(dtp); }, +}; \ No newline at end of file diff --git a/app/services/otp-auth.js b/app/services/otp-auth.js index edad956c..d3b723d2 100644 --- a/app/services/otp-auth.js +++ b/app/services/otp-auth.js @@ -1,5 +1,5 @@ // otp-auth.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -213,6 +213,14 @@ class OtpAuthService extends SiteService { return true; } + async isUserProtected (user, serviceName) { + const account = await OtpAccount.findOne({ user: user._id, service: serviceName }); + if (!account) { + return false; + } + return true; + } + async removeForUser (user) { return await OtpAccount.deleteMany({ user: user }); } diff --git a/app/services/page.js b/app/services/page.js deleted file mode 100644 index 7c8b15af..00000000 --- a/app/services/page.js +++ /dev/null @@ -1,173 +0,0 @@ -// page.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'use strict'; - -const striptags = require('striptags'); -const slug = require('slug'); - -const { SiteService } = require('../../lib/site-lib'); - -const mongoose = require('mongoose'); -const ObjectId = mongoose.Types.ObjectId; - -const Page = mongoose.model('Page'); - -class PageService extends SiteService { - - constructor (dtp) { - super(dtp, module.exports); - } - - async menuMiddleware (req, res, next) { - try { - const pages = await Page - .find({ parent: { $exists: false } }) - .select('slug menu') - .lean(); - - res.locals.mainMenu = pages - .filter((page) => !page.parent) - .map((page) => { - return { - url: `/page/${page.slug}`, - slug: page.slug, - icon: page.menu.icon, - label: page.menu.label, - order: page.menu.order, - }; - }) - .sort((a, b) => { - return a.order < b.order; - }); - return next(); - } catch (error) { - this.log.error('failed to build page menu', { error }); - return next(); - } - } - - async create (author, pageDefinition) { - const page = new Page(); - page.title = striptags(pageDefinition.title.trim()); - page.slug = this.createPageSlug(page._id, page.title); - page.content = pageDefinition.content.trim(); - page.status = pageDefinition.status || 'draft'; - - page.menu = { - icon: striptags((pageDefinition.menuIcon || 'fa-slash').trim().toLowerCase()), - label: striptags((pageDefinition.menuLabel || page.title.slice(0, 10))), - order: parseInt(pageDefinition.menuOrder || '0', 10), - }; - - if (pageDefinition.parentPageId && (pageDefinition.parentPageId !== 'none')) { - page.menu.parent = pageDefinition.parentPageId; - } - await page.save(); - - return page.toObject(); - } - - async update (page, pageDefinition) { - const NOW = new Date(); - const updateOp = { - $set: { - updated: NOW, - }, - }; - - if (pageDefinition.title) { - updateOp.$set.title = striptags(pageDefinition.title.trim()); - } - if (pageDefinition.slug) { - let pageSlug = striptags(slug(pageDefinition.slug.trim())).split('-'); - while (ObjectId.isValid(pageSlug[pageSlug.length - 1])) { - pageSlug.pop(); - } - pageSlug = pageSlug.splice(0, 4); - pageSlug.push(page._id.toString()); - updateOp.$set.slug = `${pageSlug.join('-')}`; - } - if (pageDefinition.summary) { - updateOp.$set.summary = striptags(pageDefinition.summary.trim()); - } - if (pageDefinition.content) { - updateOp.$set.content = pageDefinition.content.trim(); - } - if (pageDefinition.status) { - updateOp.$set.status = striptags(pageDefinition.status.trim()); - } - - updateOp.$set.menu = { - icon: striptags((pageDefinition.menuIcon || 'fa-slash').trim().toLowerCase()), - label: striptags((pageDefinition.menuLabel || page.title.slice(0, 10))), - order: parseInt(pageDefinition.menuOrder || '0', 10), - }; - - if (pageDefinition.parentPageId && (pageDefinition.parentPageId !== 'none')) { - updateOp.$set.menu.parent = pageDefinition.parentPageId; - } - - await Page.updateOne( - { _id: page._id }, - updateOp, - { upsert: true }, - ); - } - - async getPages (pagination, status = ['published']) { - if (!Array.isArray(status)) { - status = [status]; - } - const pages = await Page - .find({ status: { $in: status } }) - .sort({ created: -1 }) - .skip(pagination.skip) - .limit(pagination.cpp) - .lean(); - return pages; - } - - async getById (pageId) { - const page = await Page - .findById(pageId) - .select('+content') - .lean(); - return page; - } - - async getBySlug (pageSlug) { - const slugParts = pageSlug.split('-'); - const pageId = slugParts[slugParts.length - 1]; - return this.getById(pageId); - } - - async getAvailablePages (excludedPageIds) { - const search = { }; - if (excludedPageIds) { - search._id = { $nin: excludedPageIds }; - } - const pages = await Page.find(search).lean(); - return pages; - } - - async deletePage (page) { - this.log.info('deleting page', { pageId: page._id }); - await Page.deleteOne({ _id: page._id }); - } - - createPageSlug (pageId, pageTitle) { - if ((typeof pageTitle !== 'string') || (pageTitle.length < 1)) { - throw new Error('Invalid input for making a page slug'); - } - const pageSlug = slug(pageTitle.trim().toLowerCase()).split('-').slice(0, 4).join('-'); - return `${pageSlug}-${pageId}`; - } -} - -module.exports = { - slug: 'page', - name: 'page', - create: (dtp) => { return new PageService(dtp); }, -}; \ No newline at end of file diff --git a/app/services/phone.js b/app/services/phone.js new file mode 100644 index 00000000..26bd3219 --- /dev/null +++ b/app/services/phone.js @@ -0,0 +1,61 @@ +// phone.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache-2.0 + +'use strict'; + +const { + libphonenumber, + striptags, + SiteService, + SiteError, +} = require('../../lib/site-lib'); + +class PhoneService extends SiteService { + + constructor (dtp) { + super(dtp, module.exports); + } + + async processPhoneNumberInput (phoneNumberInput, country = 'US') { + const { parsePhoneNumber } = libphonenumber; + const phoneCheck = await parsePhoneNumber(striptags(phoneNumberInput.trim()), country); + + if (!phoneCheck.isValid()) { + throw new SiteError(400, 'The phone number entered is not valid'); + } + + // store everything this library provides about the new phone number + const phoneNumber = { + type: phoneCheck.getType(), + number: phoneCheck.number, + countryCallingCode: phoneCheck.countryCallingCode, + nationalNumber: phoneCheck.nationalNumber, + country: phoneCheck.country, + ext: phoneCheck.ext, + carrierCode: phoneCheck.carrierCode, + }; + + if (phoneCheck.carrierCode) { + phoneNumber.carrierCode = phoneCheck.carrierCode; + } + if (phoneCheck.ext) { + phoneNumber.ext = phoneCheck.ext; + } + + phoneNumber.display = { + national: phoneCheck.formatNational(), + international: phoneCheck.formatInternational(), + uri: phoneCheck.getURI(), + }; + + return phoneNumber; + } + +} + +module.exports = { + slug: 'phone', + name: 'phone', + create: (dtp) => { return new PhoneService(dtp); }, +}; \ No newline at end of file diff --git a/app/services/resource.js b/app/services/resource.js index 7dc51969..a4f6d845 100644 --- a/app/services/resource.js +++ b/app/services/resource.js @@ -1,5 +1,5 @@ // resource.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/session.js b/app/services/session.js index 7c6f2263..aafc4f60 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -1,5 +1,5 @@ // session.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/sms.js b/app/services/sms.js index a4d3bb2a..f086adbd 100644 --- a/app/services/sms.js +++ b/app/services/sms.js @@ -1,5 +1,5 @@ // sms.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/user-notification.js b/app/services/user-notification.js index bbdb6f3a..9e15e717 100644 --- a/app/services/user-notification.js +++ b/app/services/user-notification.js @@ -1,5 +1,5 @@ // user-notification.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/app/services/user.js b/app/services/user.js index da278372..27042eee 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -1,9 +1,11 @@ // user.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; +const path = require('path'); + const mongoose = require('mongoose'); const User = mongoose.model('User'); @@ -24,6 +26,8 @@ class UserService { this.dtp = dtp; this.log = new SiteLog(dtp, `svc:${module.exports.slug}`); + this.reservedNames = require(path.join(this.dtp.config.root, 'config', 'reserved-names')); + this.populateUser = [ { path: 'picture.large', @@ -101,13 +105,18 @@ class UserService { canChat: true, canComment: true, canReport: true, - canAuthorPages: false, - canAuthorPosts: false, + }; + + user.optIn = { + system: true, + marketing: false, }; this.log.info('creating new user account', { email: userDefinition.email }); await user.save(); + await this.sendWelcomeEmail(user); + return user.toObject(); } catch (error) { this.log.error('failed to create user', { error }); @@ -115,6 +124,71 @@ class UserService { } } + async sendWelcomeEmail (user) { + const { email: emailService } = this.dtp.services; + + /* + * Remove all pending EmailVerify tokens for the User. + */ + await emailService.removeVerificationTokensForUser(user); + + /* + * Create the new/only EmailVerify token for the user. This will be the only + * token accepted. Previous emails sent (if they were received) are invalid + * after this. + */ + const verifyToken = await emailService.createVerificationToken(user); + + /* + * Send the welcome email using the new EmailVerify token so it can + * construct a new, valid link to use for verifying the email address. + */ + const templateModel = { + site: this.dtp.config.site, + recipient: user, + emailVerifyToken: verifyToken.token, + }; + const message = { + from: process.env.DTP_EMAIL_SMTP_FROM, + to: user.email, + subject: `Welcome to ${this.dtp.config.site.name}!`, + html: await emailService.renderTemplate('welcome', 'html', templateModel), + text: await emailService.renderTemplate('welcome', 'text', templateModel), + }; + await emailService.send(message); + } + + async setEmailVerification (user, isVerified) { + await User.updateOne( + { _id: user._id }, + { + $set: { 'flags.isEmailVerified': isVerified }, + }, + ); + } + + async emailOptOut (userId, category) { + userId = mongoose.Types.ObjectId(userId); + const user = await this.getUserAccount(userId); + if (!user) { + throw new SiteError(406, 'Invalid opt-out token'); + } + + const updateOp = { $set: { } }; + switch (category) { + case 'marketing': + updateOp.$set['optIn.marketing'] = false; + break; + case 'system': + updateOp.$set['optIn.system'] = false; + break; + default: + throw new SiteError(406, 'Invalid opt-out category'); + } + + await User.updateOne({ _id: userId }, updateOp); + } + async update (user, userDefinition) { if (!user.flags.canLogin) { throw SiteError(403, 'Invalid user account operation'); @@ -125,9 +199,6 @@ class UserService { const username_lc = userDefinition.username.toLowerCase(); userDefinition.displayName = striptags(userDefinition.displayName.trim()); - if (userDefinition.bio) { - userDefinition.bio = striptags(userDefinition.bio.trim()); - } this.log.info('updating user', { userDefinition }); await User.updateOne( @@ -137,7 +208,8 @@ class UserService { username: userDefinition.username, username_lc, displayName: userDefinition.displayName, - bio: userDefinition.bio, + 'optIn.system': userDefinition['optIn.system'] === 'on', + 'optIn.marketing': userDefinition['optIn.marketing'] === 'on', }, }, ); @@ -149,9 +221,6 @@ class UserService { const username_lc = userDefinition.username.toLowerCase(); userDefinition.displayName = striptags(userDefinition.displayName.trim()); - if (userDefinition.bio) { - userDefinition.bio = striptags(userDefinition.bio.trim()); - } this.log.info('updating user for admin', { userDefinition }); await User.updateOne( @@ -161,15 +230,12 @@ class UserService { username: userDefinition.username, username_lc, displayName: userDefinition.displayName, - bio: userDefinition.bio, 'flags.isAdmin': userDefinition.isAdmin === 'on', 'flags.isModerator': userDefinition.isModerator === 'on', 'permissions.canLogin': userDefinition.canLogin === 'on', 'permissions.canChat': userDefinition.canChat === 'on', 'permissions.canComment': userDefinition.canComment === 'on', 'permissions.canReport': userDefinition.canReport === 'on', - 'permissions.canAuthorPages': userDefinition.canAuthorPages === 'on', - 'permissions.canAuthorPosts': userDefinition.canAuthorPosts === 'on', }, }, ); @@ -181,7 +247,6 @@ class UserService { const username_lc = userDefinition.username.toLowerCase(); userDefinition.displayName = striptags(userDefinition.displayName.trim()); - userDefinition.bio = striptags(userDefinition.bio.trim()); this.log.info('updating user settings', { userDefinition }); await User.updateOne( @@ -191,7 +256,7 @@ class UserService { username: userDefinition.username, username_lc, displayName: userDefinition.displayName, - bio: userDefinition.bio, + theme: userDefinition.theme || 'dtp-light', }, }, ); @@ -215,10 +280,10 @@ class UserService { { username_lc: accountUsername }, ] }) - .select('+passwordSalt +password +flags') + .select('+passwordSalt +password +flags +optIn +permissions') .lean(); if (!user) { - throw new SiteError(404, 'Member account not found'); + throw new SiteError(404, 'Member credentials are invalid'); } const maskedPassword = crypto.maskPassword( @@ -226,7 +291,7 @@ class UserService { account.password, ); if (maskedPassword !== user.password) { - throw new SiteError(403, 'Account credentials do not match'); + throw new SiteError(403, 'Member credentials are invalid'); } // remove these critical fields from the user object @@ -328,7 +393,7 @@ class UserService { async getUserAccount (userId) { const user = await User .findById(userId) - .select('+email +flags +permissions +picture') + .select('+email +flags +permissions +optIn +picture') .populate(this.populateUser) .lean(); if (!user) { @@ -345,7 +410,7 @@ class UserService { const users = await User .find(search) .sort({ username_lc: 1 }) - .select('+email +flags +permissions') + .select('+email +flags +permissions +optIn') .skip(pagination.skip) .limit(pagination.cpp) .lean() @@ -375,7 +440,7 @@ class UserService { username = username.trim().toLowerCase(); const user = await User .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture header') + .select('_id created username username_lc displayName picture header') .populate(this.populateUser) .lean(); return user; @@ -384,7 +449,7 @@ class UserService { async getRecent (maxCount = 3) { const users = User .find() - .select('_id created username username_lc displayName bio picture') + .select('_id created username username_lc displayName picture') .sort({ created: -1 }) .limit(maxCount) .lean(); @@ -468,28 +533,7 @@ class UserService { if (!username || (typeof username !== 'string') || (username.length === 0)) { throw new SiteError(406, 'Invalid username'); } - const reservedNames = [ - 'about', - 'admin', - 'auth', - 'digitaltelepresence', - 'dist', - 'dtp', - 'fontawesome', - 'fonts', - 'img', - 'image', - 'less', - 'manifest.json', - 'moment', - 'newsletter', - 'numeral', - 'socket.io', - 'uikit', - 'user', - 'welcome', - ]; - if (reservedNames.includes(username.trim().toLowerCase())) { + if (this.reservedNames.includes(username.trim().toLowerCase())) { throw new SiteError(403, 'That username is reserved for system use'); } diff --git a/app/templates/common/html/footer.pug b/app/templates/common/html/footer.pug index 8f74b15e..2bf23cd7 100644 --- a/app/templates/common/html/footer.pug +++ b/app/templates/common/html/footer.pug @@ -1,10 +1,8 @@ .common-footer - - p This email was sent to #{user.email} because you selected to receive emails from LocalRedAction.com. You can #[a(href=`https://localredaction.com/opt-out/${voter._id}/email`) opt out] at any time to stop receiving these emails. - - p You can request to stop receiving these emails in writing at: - address - div Local Red Action - div P.O. Box ######## - div McKees Rocks, PA 15136 - div USA \ No newline at end of file + p This email was sent to #{recipient.email} because you selected to receive emails from #{site.name}. You can #[a(href=`https://${site.domain}/email/opt-out?u=${recipient._id}&c=marketing`) opt out] at any time to stop receiving these emails. + //- p You can request to stop receiving these emails in writing at: + //- address + //- div Digital Telepresence, LLC + //- div P.O. Box ######## + //- div McKees Rocks, PA 15136 + //- div USA \ No newline at end of file diff --git a/app/templates/common/html/header.pug b/app/templates/common/html/header.pug index abd3ff71..0b1cf552 100644 --- a/app/templates/common/html/header.pug +++ b/app/templates/common/html/header.pug @@ -1 +1,2 @@ -.greeting Dear #{voter.name}, \ No newline at end of file +.common-title= emailTitle || `Greetings from ${site.name}!` +.common-slogan= site.description \ No newline at end of file diff --git a/app/templates/common/text/footer.pug b/app/templates/common/text/footer.pug index a9e38f69..cddfef2a 100644 --- a/app/templates/common/text/footer.pug +++ b/app/templates/common/text/footer.pug @@ -1,9 +1,10 @@ +| | - - - -| This email was sent to #{user.email} because you selected to receive emails from LocalRedAction.com. Visit #{`https://localredaction.com/opt-out/${voter._id}/email`} to opt out and stop receiving these emails. +| This email was sent to #{recipient.email} because you selected to receive emails from #{site.name}. Visit #{`https://${site.domain}/email/opt-out?u=${recipient._id}&c=marketing`} to opt out and stop receiving these emails. | -| You can request to stop receiving these emails in writing at: -| -| Local Red Action -| P.O. Box ######## -| McKees Rocks, PA 15136 -| USA \ No newline at end of file +//- | You can request to stop receiving these emails in writing at: +//- | +//- | #{site.company} +//- | P.O. Box ######## +//- | McKees Rocks, PA 15136 +//- | USA \ No newline at end of file diff --git a/app/templates/common/text/header.pug b/app/templates/common/text/header.pug index e0c2dc5f..621d1671 100644 --- a/app/templates/common/text/header.pug +++ b/app/templates/common/text/header.pug @@ -1 +1,2 @@ -| Dear #{voter.name}, \ No newline at end of file +| Dear #{recipient.displayName || recipient.username}, +| \ No newline at end of file diff --git a/app/templates/html/user-email.pug b/app/templates/html/user-email.pug new file mode 100644 index 00000000..ca41792c --- /dev/null +++ b/app/templates/html/user-email.pug @@ -0,0 +1,3 @@ +extends ../layouts/html/system-message +block message-body + .content-message!= htmlMessage \ No newline at end of file diff --git a/app/templates/html/welcome.pug b/app/templates/html/welcome.pug index ff8449dd..eceb7e60 100644 --- a/app/templates/html/welcome.pug +++ b/app/templates/html/welcome.pug @@ -1,27 +1,4 @@ -doctype html -html(lang='en') - head - meta(charset='UTF-8') - meta(name='viewport', content='width=device-width, initial-scale=1.0') - meta(name='description', content= pageDescription || siteDescription) - - title= pageTitle ? `${pageTitle} | ${site.name}` : site.name - - style(type="text/css"). - html, body { - margin: 0; - padding: 0; - } - .greeting { font-size: 1.5em; margin-bottom: 16px; } - .message {} - - body - - include ../common/html/header - - .message - p Welcome to #{service.name}! Please visit #[a(href=`https://localredaction.com/verify?t=${emailVerifyToken}`)= `https://localredaction.com/verify?t=${emailVerifyToken}`] to verify your email address. - - p Thank you for supporting your local Republican committee and candidates! - - include ../common/html/footer \ No newline at end of file +extends ../layouts/html/system-message +block content + p Welcome to #{site.name}! Please visit #[a(href=`https://${site.domain}/email/verify?t=${emailVerifyToken}`)= `https://${site.domain}/email/verify?t=${emailVerifyToken}`] to verify your email address and enable all features on your new account. + p If you did not sign up for a new account at #{site.name}, please disregard this message. \ No newline at end of file diff --git a/app/templates/layouts/html/system-message.pug b/app/templates/layouts/html/system-message.pug new file mode 100644 index 00000000..1e8b599b --- /dev/null +++ b/app/templates/layouts/html/system-message.pug @@ -0,0 +1,106 @@ +doctype html +html(lang='en') + head + meta(charset='UTF-8') + meta(name='viewport', content='width=device-width, initial-scale=1.0') + meta(name='description', content= pageDescription || siteDescription) + + title= pageTitle ? `${pageTitle} | ${site.name}` : site.name + + style(type="text/css"). + html, body { + padding: 0; + margin: 0; + background-color: #ffffff; + color: #1a1a1a; + } + + section { + padding: 20px 0; + background-color: #ffffff; + color: #1a1a1a; + } + + section.section-muted { + background-color: #f8f8f8; + color: #2a2a2a; + } + + .common-title { + max-width: 640px; + margin: 0 auto 8px auto; + font-size: 1.5em; + } + + .common-greeting { + max-width: 640px; + margin: 0 auto 20px auto; + font-size: 1.1em; + } + + .common-slogan { + max-width: 640px; + margin: 0 auto; + font-size: 1.1em; + font-style: italic; + } + + .content-message { + max-width: 640px; + margin: 0 auto; + background: white; + color: black; + font-size: 14px; + } + + .content-signature { + max-width: 640px; + margin: 0 auto; + background: white; + color: black; + font-size: 14px; + } + + .common-footer { + max-width: 640px; + margin: 0 auto; + font-size: 10px; + } + + .channel-icon { + border-radius: 8px; + } + + .action-button { + padding: 6px 20px; + margin: 24px 0; + border: none; + border-radius: 20px; + outline: none; + background-color: #1093de; + color: #ffffff; + font-size: 16px; + font-weight: bold; + } + + body + + include ../library + + section.section-muted + include ../../common/html/header + + section + .common-greeting + div Dear #{recipient.displayName || recipient.username}, + + block message-body + .content-message + block content + + .content-signature + p Thank you for your continued support! + p The #{site.name} team. + + section.section-muted + include ../../common/html/footer \ No newline at end of file diff --git a/app/templates/layouts/library.pug b/app/templates/layouts/library.pug new file mode 100644 index 00000000..89c69a2d --- /dev/null +++ b/app/templates/layouts/library.pug @@ -0,0 +1,4 @@ +- + function formatCount (count) { + return numeral(count).format((count > 1000) ? '0,0.0a' : '0,0'); + } \ No newline at end of file diff --git a/app/templates/layouts/text/system-message.pug b/app/templates/layouts/text/system-message.pug new file mode 100644 index 00000000..9777eb71 --- /dev/null +++ b/app/templates/layouts/text/system-message.pug @@ -0,0 +1,10 @@ +include ../library +include ../../common/text/header +| +block content +| +| Thank you for your continued support! +| +| The #{site.name} team. +| +include ../../common/text/footer \ No newline at end of file diff --git a/app/templates/text/user-email.pug b/app/templates/text/user-email.pug new file mode 100644 index 00000000..ec95e3e4 --- /dev/null +++ b/app/templates/text/user-email.pug @@ -0,0 +1,5 @@ +extends ../layouts/text/system-message +block content + | + | #{textMessage} + | \ No newline at end of file diff --git a/app/templates/text/welcome.pug b/app/templates/text/welcome.pug index 678390ac..1127acf9 100644 --- a/app/templates/text/welcome.pug +++ b/app/templates/text/welcome.pug @@ -1,7 +1,7 @@ -include ../common/text/header -| -| Welcome to #{service.name}! Please visit #[a(href=`https://localredaction.com/verify?t=${emailVerifyToken}`)= `https://localredaction.com/verify?t=${emailVerifyToken}`] to verify your email address. -| -| Thank you for supporting your local Republican committee and candidates! -| -include ../common/text/footer \ No newline at end of file +extends ../layouts/text/system-message +block content + | + | Welcome to #{site.name}! Please visit #{`https://${site.domain}/email/verify?t=${emailVerifyToken}`} to verify your email address and enable all features on your new account. + | + | If you did not sign up for a new account at #{site.name}, please disregard this message. + | \ No newline at end of file diff --git a/app/views/admin/category/editor.pug b/app/views/admin/category/editor.pug deleted file mode 100644 index ad57d935..00000000 --- a/app/views/admin/category/editor.pug +++ /dev/null @@ -1,17 +0,0 @@ -extends ../layouts/main -block content - - - var formAction = category ? `/admin/category/${category._id}` : '/admin/category'; - - pre= JSON.stringify(category, null, 2) - - form(method="POST", action= formAction).uk-form - .uk-margin - label(for="name").uk-form-label Category Name - input(id="name", name="name", type="text", placeholder="Enter category name", value= category ? category.name : undefined).uk-input - - .uk-margin - label(for="description").uk-form-label Description - textarea(id="description", name="description", rows="3", placeholder="Enter category description").uk-textarea= category ? category.description : undefined - - button(type="submit").uk-button.uk-button-primary= category ? 'Update Category' : 'Create Category' \ No newline at end of file diff --git a/app/views/admin/category/index.pug b/app/views/admin/category/index.pug deleted file mode 100644 index d8e68e47..00000000 --- a/app/views/admin/category/index.pug +++ /dev/null @@ -1,21 +0,0 @@ -extends ../layouts/main -block content - - .uk-margin - div(uk-grid).uk-flex-middle - .uk-width-expand - h2 Category Manager - .uk-width-auto - a(href="/admin/category/create").uk-button.uk-button-primary - span - i.fas.fa-plus - span.uk-margin-small-left Add category - - .uk-margin - if Array.isArray(categories) && (categories.length > 0) - uk.uk-list - each category in categories - li - a(href=`/admin/category/${category._id}`)= category.name - else - h4 There are no categories. \ No newline at end of file diff --git a/app/views/admin/components/menu.pug b/app/views/admin/components/menu.pug index c0bf6962..ad77d699 100644 --- a/app/views/admin/components/menu.pug +++ b/app/views/admin/components/menu.pug @@ -14,24 +14,6 @@ ul.uk-nav.uk-nav-default li.uk-nav-divider - li(class={ 'uk-active': (adminView === 'post') }) - a(href="/admin/post") - span.nav-item-icon - i.fas.fa-pen - span.uk-margin-small-left Posts - li(class={ 'uk-active': (adminView === 'page') }) - a(href="/admin/page") - span.nav-item-icon - i.fas.fa-file - span.uk-margin-small-left Pages - li(class={ 'uk-active': (adminView === 'newsletter') }) - a(href="/admin/newsletter") - span.nav-item-icon - i.fas.fa-newspaper - span.uk-margin-small-left Newsletter - - li.uk-nav-divider - li(class={ 'uk-active': (adminView === 'user') }) a(href="/admin/user") span.nav-item-icon @@ -45,6 +27,11 @@ ul.uk-nav.uk-nav-default li.uk-nav-divider + li(class={ 'uk-active': (adminView === 'core-node') }) + a(href="/admin/core-node") + span.nav-item-icon + i.fas.fa-project-diagram + span.uk-margin-small-left Core Nodes li(class={ 'uk-active': (adminView === 'host') }) a(href="/admin/host") span.nav-item-icon diff --git a/app/views/admin/core-node/connect.pug b/app/views/admin/core-node/connect.pug new file mode 100644 index 00000000..3dc1da53 --- /dev/null +++ b/app/views/admin/core-node/connect.pug @@ -0,0 +1,18 @@ +extends ../layouts/main +block content + + form(method="POST", action="/admin/core-node/connect").uk-form + .uk-card.uk-card-default.uk-card-small + .uk-card-header + h1.uk-card-title Connect to New Core + + .uk-card-body + .uk-margin + label(for="host").uk-form-label Address + input(id="host", name="host", placeholder="Enter host name or address", required).uk-input + .uk-margin + label(for="port").uk-form-label Port Number + input(id="port", name="port", min="1", max="65535", step="1", value="4200", required).uk-input + + .uk-card-footer + button(type="submit").uk-button.uk-button-primary Send Request \ No newline at end of file diff --git a/app/views/admin/core-node/index.pug b/app/views/admin/core-node/index.pug new file mode 100644 index 00000000..d0797058 --- /dev/null +++ b/app/views/admin/core-node/index.pug @@ -0,0 +1,14 @@ +extends ../layouts/main +block content + + h1 Core Nodes + a(href="/admin/core-node/connect").uk-button.uk-button-primary Connect Core + + p You can register with one or more Core nodes to exchange information with those nodes. + + if Array.isArray(coreNodes) && (coreNodes.length > 0) + ul.uk-list + each node in coreNodes + pre= JSON.stringify(node, null, 2) + else + p There are no registered core nodes. \ No newline at end of file diff --git a/app/views/admin/job-queue/job-view.pug b/app/views/admin/job-queue/job-view.pug new file mode 100644 index 00000000..65bd0611 --- /dev/null +++ b/app/views/admin/job-queue/job-view.pug @@ -0,0 +1,76 @@ +extends ../layouts/main +block content + + .uk-margin + .uk-card.uk-card-secondary.uk-card-small + .uk-card-header + h1.uk-card-title #{job.name} id: #{job.id} + + .uk-card-body + .uk-margin + progress(value= job.progress, max= 100).uk-progress + .uk-margin + div(uk-grid) + .uk-width-auto attempt: #{job.attemptsMade} + .uk-width-auto attempts: #{job.opts.attempts} + .uk-width-auto ts: #{moment(job.timestamp).format('YYYY-MM-DD hh:mm:ss a')} + .uk-width-auto proc: #{moment(job.processedOn).format('YYYY-MM-DD hh:mm:ss a')} + + if job.finishedOn + .uk-width-auto fin: #{job.finishedOn} + + if job.delay > 0 + .uk-width-auto delay: #{job.delay} + + if job.opts.removeOnComplete + .uk-width-auto.uk-text-success remove on complete + + if job.opts.removeOnFail + .uk-width-auto.uk-text-danger remove on fail + + if job.data + h4 Job data + pre= JSON.stringify(job.data, null, 2) + + if job.failedReason + h4.uk-text-danger Failed reason + div= job.failedReason + + if Array.isArray(jobLogs) && (jobLogs.length > 0) + h4 Log + each log in jobLogs + div= log + + if Array.isArray(job.stacktrace) && (job.stacktrace.length > 0) + h4 Stacktrace + each trace in job.stacktrace + pre= trace + + .uk-card-footer + div(uk-grid) + .uk-width-expand + div(uk-grid) + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'remove', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-danger Remove + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'discard', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-danger Discard + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'retry', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-primary Retry \ No newline at end of file diff --git a/app/views/admin/newsletter/editor.pug b/app/views/admin/newsletter/editor.pug deleted file mode 100644 index 6a212e6b..00000000 --- a/app/views/admin/newsletter/editor.pug +++ /dev/null @@ -1,66 +0,0 @@ -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', - image_class_list: [ - { title: 'Body Image', value: 'dtp-image-body' }, - { title: 'Title Image', value: 'dtp-image-title' }, - ], - convert_urls: false, - skin: "oxide-dark", - content_css: "dark", - }); - - window.dtp.app.editor = editors[0]; - }); \ No newline at end of file diff --git a/app/views/admin/newsletter/index.pug b/app/views/admin/newsletter/index.pug deleted file mode 100644 index b0dec461..00000000 --- a/app/views/admin/newsletter/index.pug +++ /dev/null @@ -1,39 +0,0 @@ -extends ../layouts/main -block content - - .uk-margin - div(uk-grid) - .uk-width-expand - h1.uk-text-truncate Newsletters - .uk-width-auto - a(href="/admin/newsletter/compose").uk-button.dtp-button-primary - +renderButtonIcon('fa-plus', 'New Newsletter') - - .uk-margin - if (Array.isArray(newsletters) && (newsletters.length > 0)) - - ul.uk-list - - each newsletter in newsletters - - li(data-newsletter-id= newsletter._id) - div(uk-grid).uk-grid-small.uk-flex-middle - .uk-width-expand - a(href=`/admin/newsletter/${newsletter._id}`).uk-display-block.uk-text-large.uk-text-truncate= newsletter.title - - .uk-width-auto - div(uk-grid).uk-grid-small - .uk-width-auto - button( - type="button", - data-newsletter-id= newsletter._id, - data-newsletter-title= newsletter.title, - onclick="return dtp.adminApp.deleteNewsletter(event);", - ).uk-button.dtp-button-danger - +renderButtonIcon('fa-trash', 'Delete') - - .uk-width-auto - button(type="button").uk-button.dtp-button-default - +renderButtonIcon('fa-paper-plane', 'Send') - else - div There are no newsletters at this time. \ No newline at end of file diff --git a/app/views/admin/page/editor.pug b/app/views/admin/page/editor.pug deleted file mode 100644 index f1ea69a4..00000000 --- a/app/views/admin/page/editor.pug +++ /dev/null @@ -1,93 +0,0 @@ -extends ../layouts/main -block content - - - var actionUrl = page ? `/admin/page/${page._id}` : `/admin/page`; - - form(method="POST", action= actionUrl).uk-form - div(uk-grid).uk-grid-small - div(class="uk-width-1-1 uk-width-2-3@m") - .uk-margin - label(for="content").uk-form-label Page body - textarea(id="content", name="content", rows="4").uk-textarea= page ? page.content : undefined - - div(class="uk-width-1-1 uk-width-1-3@m") - .uk-margin - label(for="title").uk-form-label Page title - input(id="title", name="title", type="text", placeholder= "Enter page title", value= page ? page.title : undefined).uk-input - .uk-margin - label(for="slug").uk-form-label URL slug - - - var pageSlug; - pageSlug = page ? (page.slug || 'enter-slug-here').split('-') : ['enter', 'slug', 'here', '']; - pageSlug.pop(); - pageSlug = pageSlug.join('-'); - input(id="slug", name="slug", type="text", placeholder= "Enter page URL slug", value= page ? pageSlug : undefined).uk-input - .uk-text-small The slug is used in the link to the page https://#{site.domain}/page/#{pageSlug} - div(uk-grid) - .uk-width-auto - button(type="submit").uk-button.dtp-button-primary= page ? 'Update page' : 'Create page' - .uk-margin - label(for="status").uk-form-label Status - select(id="status", name="status").uk-select - option(value="draft", selected= page ? page.status === 'draft' : true) Draft - option(value="published", selected= page ? page.status === 'published' : false) Published - option(value="archived", selected= page ? page.status === 'archived' : false) Archived - - fieldset - legend Menu - .uk-margin - label(for="menu-icon").uk-form-label Menu item icon - input(id="menu-icon", name="menuIcon", type="text", maxlength="80", placeholder="Enter icon class", value= page ? page.menu.icon : undefined).uk-input - .uk-text-small Visit #[a(href="https://fontawesome.com/v5.15/icons?d=gallery&p=2&q=blog&m=free", target="_blank") FontAwesome] for a list of usable icons. - .uk-margin - label(for="menu-label").uk-form-label Menu item label - input(id="menu-label", name="menuLabel", type="text", maxlength="80", placeholder="Enter label", value= page ? page.menu.label : undefined).uk-input - .uk-margin - label(for="menu-order").uk-form-label Menu item order - input(id="menu-order", name="menuOrder", type="number", min="0", value= page ? page.menu.order : 0).uk-input - if Array.isArray(availablePages) && (availablePages.length > 0) - .uk-margin - label(for="menu-parent").uk-form-label Parent page - select(id="menu-parent", name="parentPageId").uk-select - option(value= "none") --- Select parent page --- - each menuPage in availablePages - option(value= menuPage._id)= menuPage.title -block viewjs - script(src="/tinymce/tinymce.min.js") - script. - window.addEventListener('dtp-load', async ( ) => { - const toolbarItems = [ - 'undo redo', - 'formatselect visualblocks', - 'bold italic backcolor', - 'alignleft aligncenter alignright alignjustify', - 'bullist numlist outdent indent removeformat', - 'link image code', - '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', - height: 500, - menubar: false, - plugins: pluginItems.join(' '), - toolbar: toolbarItems.join('|'), - branding: false, - images_upload_url: '/image/tinymce', - image_class_list: [ - { title: 'Body Image', value: 'dtp-image-body' }, - { title: 'Title Image', value: 'dtp-image-title' }, - ], - convert_urls: false, - skin: "oxide-dark", - content_css: "dark", - }); - - window.dtp.app.editor = editors[0]; - }); \ No newline at end of file diff --git a/app/views/admin/page/index.pug b/app/views/admin/page/index.pug deleted file mode 100644 index 84e1f50d..00000000 --- a/app/views/admin/page/index.pug +++ /dev/null @@ -1,43 +0,0 @@ -extends ../layouts/main -block content - - .uk-margin - div(uk-grid) - .uk-width-expand - h1.uk-text-truncate Pages - .uk-width-auto - a(href="/admin/page/compose").uk-button.dtp-button-primary - +renderButtonIcon('fa-plus', 'New Page') - - .uk-margin - if (Array.isArray(pages) && (pages.length > 0)) - - ul.uk-list - - each page in pages - - li(data-page-id= page._id) - div(uk-grid).uk-grid-small.uk-flex-middle - .uk-width-expand - a(href=`/page/${page.slug}`).uk-display-block.uk-text-large.uk-text-truncate #{page.title} - - .uk-width-auto - div(uk-grid).uk-grid-small.uk-flex-middle - .uk-width-auto(class={ - 'uk-text-info': (page.status === 'draft'), - 'uk-text-success': (page.status === 'published'), - 'uk-text-danger': (page.status === 'archived'), - })= page.status - .uk-width-auto - a(href=`/admin/page/${page._id}`).uk-button.dtp-button-primary - +renderButtonIcon('fa-pen', 'Edit') - .uk-width-auto - button( - type="button", - data-page-id= page._id, - data-page-title= page.title, - onclick="return dtp.adminApp.deletePage(event);", - ).uk-button.dtp-button-danger - +renderButtonIcon('fa-trash', 'Delete') - else - div There are no pages at this time. \ No newline at end of file diff --git a/app/views/admin/post/editor.pug b/app/views/admin/post/editor.pug deleted file mode 100644 index 405c330d..00000000 --- a/app/views/admin/post/editor.pug +++ /dev/null @@ -1,108 +0,0 @@ -extends ../layouts/main -block vendorcss - link(rel='stylesheet', href=`/cropperjs/cropper.min.css?v=${pkg.version}`) -block vendorjs - script(src=`/cropperjs/cropper.min.js?v=${pkg.version}`) -block content - - include ../../components/file-upload-image - - - var actionUrl = post ? `/admin/post/${post._id}` : `/admin/post`; - - form(method="POST", action= actionUrl).uk-form - div(uk-grid).uk-grid-small - div(class="uk-width-1-1 uk-width-2-3@m") - .uk-margin - label(for="content").uk-form-label Post body - textarea(id="content", name="content", rows="4").uk-textarea= post ? post.content : undefined - - div(class="uk-width-1-1 uk-width-1-3@m") - .uk-margin - label(for="title").uk-form-label Post title - input(id="title", name="title", type="text", placeholder= "Enter post title", value= post ? post.title : undefined).uk-input - .uk-margin - label(for="slug").uk-form-label URL slug - - - var postSlug; - if (post) { - postSlug = post.slug.split('-'); - postSlug.pop(); - postSlug = postSlug.join('-'); - } - input(id="slug", name="slug", type="text", placeholder= "Enter post URL slug", value= post ? postSlug : undefined).uk-input - .uk-text-small The slug is used in the link to the page https://#{site.domain}/post/#{post ? post.slug : 'your-slug-here'} - .uk-margin - label(for="summary").uk-form-label Post summary - textarea(id="summary", name="summary", rows="4", placeholder= "Enter post summary (text only, no HTML)").uk-textarea= post ? post.summary : undefined - div(uk-grid) - .uk-width-auto - button(type="submit").uk-button.dtp-button-primary= post ? 'Update post' : 'Create post' - .uk-margin - label(for="status").uk-form-label Status - select(id="status", name="status").uk-select - option(value="draft", selected= post ? post.status === 'draft' : true) Draft - option(value="published", selected= post ? post.status === 'published' : false) Published - option(value="archived", selected= post ? post.status === 'archived' : false) Archived - .uk-margin - div(uk-grid).uk-grid-small - .uk-width-auto - label - input(id="enable-comments", name="enableComments", type="checkbox", checked= post ? post.flags.enableComments : true).uk-checkbox - | Enable comments - .uk-width-auto - label - input(id="is-featured", name="isFeatured", type="checkbox", checked= post ? post.flags.isFeatured : false).uk-checkbox - | Featured - - if post - .uk-margin - label(for="post-image-file").uk-form-label Feature Image - +renderFileUploadImage( - `/admin/post/${post._id}/image`, - 'post-image-upload', - 'post-image-file', - 'responsive', - `/img/default-poster.jpg`, - post.image, - { aspectRatio: 16 / 9 }, - ) - -block viewjs - script(src="/tinymce/tinymce.min.js") - script. - window.addEventListener('dtp-load', async ( ) => { - const toolbarItems = [ - 'undo redo', - 'formatselect visualblocks', - 'bold italic backcolor', - 'alignleft aligncenter alignright alignjustify', - 'bullist numlist outdent indent removeformat', - 'link image code', - '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', - height: 500, - menubar: false, - plugins: pluginItems.join(' '), - toolbar: toolbarItems.join('|'), - branding: false, - images_upload_url: '/image/tinymce', - image_class_list: [ - { title: 'Body Image', value: 'dtp-image-body' }, - { title: 'Title Image', value: 'dtp-image-title' }, - ], - convert_urls: false, - skin: "oxide-dark", - content_css: "dark", - }); - - window.dtp.app.editor = editors[0]; - }); \ No newline at end of file diff --git a/app/views/admin/post/index.pug b/app/views/admin/post/index.pug deleted file mode 100644 index d900b836..00000000 --- a/app/views/admin/post/index.pug +++ /dev/null @@ -1,53 +0,0 @@ -extends ../layouts/main -block content - - .uk-margin - div(uk-grid) - .uk-width-expand - h1.uk-text-truncate Posts - .uk-width-auto - a(href="/admin/post/compose").uk-button.dtp-button-primary - +renderButtonIcon('fa-plus', 'New Post') - - .uk-margin - if (Array.isArray(posts) && (posts.length > 0)) - - ul.uk-list - - each post in posts - - li(data-post-id= post._id) - div(uk-grid).uk-grid-small.uk-flex-middle - .uk-width-expand - a(href=`/post/${post.slug}`).uk-display-block.uk-text-large.uk-text-truncate #{post.title} - .uk-text-small - div(uk-grid).uk-grid-small - .uk-width-auto - span published: #{moment(post.created).format('MMM DD, YYYY [at] hh:mm:ss a')} - if post.updated - .uk-width-auto - span last update: #{moment(post.updated).format('MMM DD, YYYY [at] hh:mm:ss a')} - - .uk-width-auto - div(uk-grid).uk-grid-small.uk-flex-middle - if post.flags.isFeatured - .uk-width-auto - i(style="color: yellow;").fas.fa-star - .uk-width-auto(class={ - 'uk-text-info': (post.status === 'draft'), - 'uk-text-success': (post.status === 'published'), - 'uk-text-danger': (post.status === 'archived'), - })= post.status - .uk-width-auto - a(href=`/admin/post/${post._id}`).uk-button.dtp-button-primary - +renderButtonIcon('fa-pen', 'Edit') - .uk-width-auto - button( - type="button", - data-post-id= post._id, - data-post-title= post.title, - onclick="return dtp.adminApp.deletePost(event);", - ).uk-button.dtp-button-danger - +renderButtonIcon('fa-trash', 'Delete') - else - div There are no posts at this time. \ No newline at end of file diff --git a/app/views/admin/settings/editor.pug b/app/views/admin/settings/editor.pug index 306f0286..a166b13f 100644 --- a/app/views/admin/settings/editor.pug +++ b/app/views/admin/settings/editor.pug @@ -13,6 +13,7 @@ block content .uk-margin label(for="company").uk-form-label Company name input(id="company", name="company", type="text", maxlength="200", placeholder="Enter company name", value= site.company).uk-input + fieldset legend Featured Embed textarea(id="featured-embed", name="featuredEmbed", rows="4").uk-textarea.uk-resize-vertical= site.featuredEmbed @@ -73,4 +74,5 @@ block content div(class="uk-width-1-1 uk-width-1-2@m uk-width-1-3@xl") label(for="spreaker-url").uk-form-label Spreaker URL input(id="spreaker-url", name="spreakerUrl", type="url", placeholder="Enter Spreaker URL", value= site.spreakerUrl).uk-input + button(type="submit").uk-button.dtp-button-primary Save Settings \ No newline at end of file diff --git a/app/views/admin/user/form.pug b/app/views/admin/user/form.pug index 626a5a32..f8539953 100644 --- a/app/views/admin/user/form.pug +++ b/app/views/admin/user/form.pug @@ -18,10 +18,6 @@ block content a(href=`/user/${userAccount._id}`) @#{userAccount.username} .uk-card-body - .uk-margin - label(for="bio").uk-form-label.sr-only Bio - textarea(id="bio", name="bio", rows="4", placeholder= "Bio is empty", disabled= !userAccount.bio || (userAccount.bio.length === 0)).uk-textarea.uk-resize-vertical= userAccount.bio - .uk-margin div(uk-grid) div(class="uk-width-1-1 uk-width-1-2@m") diff --git a/app/views/category/components/list-item.pug b/app/views/category/components/list-item.pug deleted file mode 100644 index 41f5f552..00000000 --- a/app/views/category/components/list-item.pug +++ /dev/null @@ -1,6 +0,0 @@ -mixin renderCategoryListItem (category) - a(href=`/category/${category.slug}`).uk-display-block.uk-link-reset - img(src='/img/default-poster.jpg').uk-display-block.uk-margin-small.responsive.uk-border-rounded - .uk-link-reset.uk-text-bold= category.name - .uk-ling-reset.uk-text-muted #{numeral(category.stats.liveChannelCount).format("0,0")} live channels - .uk-ling-reset.uk-text-muted #{numeral(category.stats.currentViewerCount).format("0,0.0a")} viewers \ No newline at end of file diff --git a/app/views/category/home.pug b/app/views/category/home.pug deleted file mode 100644 index da7cb090..00000000 --- a/app/views/category/home.pug +++ /dev/null @@ -1,17 +0,0 @@ -extends ../layouts/main -block content - - include components/list-item - - section.uk-section.uk-section-default.uk-section-small - .uk-container.uk-container-expand - - if Array.isArray(categories) && (categories.length > 0) - div(uk-grid).uk-flex-center.uk-grid-small - each category in categories - .uk-width-auto - .uk-width-medium - .uk-margin - +renderCategoryListItem(category) - else - h4.uk-text-center There are no categories or the system is down for maintenance. \ No newline at end of file diff --git a/app/views/category/view.pug b/app/views/category/view.pug deleted file mode 100644 index 7cd7b4d5..00000000 --- a/app/views/category/view.pug +++ /dev/null @@ -1,32 +0,0 @@ -extends ../layouts/main -block content - - include ../channel/components/list-item - - section(style="font: Verdana;").uk-section.uk-section-muted.uk-section-small - .uk-container - div(uk-grid).uk-grid-small - .uk-width-auto - img(src="/img/default-poster.jpg").uk-width-small - .uk-width-expand - h1.uk-margin-remove.uk-padding-remove= category.name - div= category.description - div(uk-grid).uk-grid-small - .uk-width-auto #{category.stats.streamCount} live shows. - .uk-width-auto #{category.stats.viewerCount} total viewers. - - section.uk-section.uk-section-default - .uk-container - if Array.isArray(channels) && (channels.length > 0) - div(uk-grid).uk-flex-center.uk-grid-small - each channel in channels - div(class="uk-width-1-1 uk-width-1-2@s uk-width-1-3@m uk-width-1-4@l") - +renderChannelListItem(channel) - else - .uk-text-lead No channels in this category, check back later. - include ../components/back-button - - - //- pre= JSON.stringify(category, null, 2) - pre= JSON.stringify(category, null, 2) - pre= JSON.stringify(channels, null, 2) diff --git a/app/views/components/navbar.pug b/app/views/components/navbar.pug index b6c887c4..17ac0eac 100644 --- a/app/views/components/navbar.pug +++ b/app/views/components/navbar.pug @@ -7,28 +7,20 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top div(class="uk-visible@m").uk-navbar-left //- Site icon a(href="/").uk-navbar-item - img(src=`/img/icon/icon-48x48.png`) + img(src=`/img/icon/${site.domainKey}/icon-36x36.png`) //- Site name ul.uk-navbar-nav li(class={ 'uk-active': currentView === 'home' }) a(href="/", title= "Home") +renderButtonIcon('fa-home', 'Home') - each menuItem in mainMenu - li(class={ 'uk-active': (pageSlug === menuItem.slug) }) - a(href= menuItem.url, title= menuItem.label) - +renderButtonIcon(menuItem.icon || 'fa-file', menuItem.label) div(class="uk-hidden@m").uk-navbar-center //- Site name ul.uk-navbar-nav li a(href="/").uk-navbar-item - img(src=`/img/icon/icon-48x48.png`) - - each menuItem in mainMenu - li - a(href= menuItem.url, title= menuItem.label)= menuItem.label + img(src=`/img/icon/${site.domainKey}/icon-36x36.png`) .uk-navbar-right if user diff --git a/app/views/components/off-canvas.pug b/app/views/components/off-canvas.pug index 0df23ee3..4a535b16 100644 --- a/app/views/components/off-canvas.pug +++ b/app/views/components/off-canvas.pug @@ -20,11 +20,6 @@ mixin renderMenuItem (iconClass, label) a(href='/').uk-display-block +renderMenuItem('fa-home', 'Home') - each menuItem in mainMenu - li(class={ 'uk-active': (pageSlug === menuItem.slug) }) - a(href= menuItem.url, title= menuItem.label) - +renderMenuItem(menuItem.icon || 'fa-file', menuItem.label) - if user li.uk-nav-header Member Menu diff --git a/app/views/components/page-footer.pug b/app/views/components/page-footer.pug index b179cd84..01e1b2cd 100644 --- a/app/views/components/page-footer.pug +++ b/app/views/components/page-footer.pug @@ -82,8 +82,7 @@ section.uk-section.uk-section-muted.uk-section-small.dtp-site-footer img(src="/img/social-icons/discord.svg", style="width: auto; height: 1em;") span.uk-margin-small-left Discord - .uk-width-medium.uk-margin-auto - hr - +section.uk-section.uk-section-muted.uk-section-small.dtp-site-footer + .uk-container.uk-text-small.uk-text-center div Copyright © 2021 #[+renderSiteLink()] div All Rights Reserved \ No newline at end of file diff --git a/app/views/components/page-sidebar.pug b/app/views/components/page-sidebar.pug index 5ee283f2..ad7966e8 100644 --- a/app/views/components/page-sidebar.pug +++ b/app/views/components/page-sidebar.pug @@ -1,15 +1,3 @@ -mixin renderSidebarEpisode(episode) - .uk-card.uk-card-secondary.uk-card-small.uk-card-hover - - .uk-card-media-top - a(href= episode.url, target="_blank", title="Watch on Gab TV") - img(src=episode.image).responsive - - .uk-card-body - .uk-card-title.uk-margin-remove.uk-text-truncate - a(href= episode.url, target="_blank", title= `Watch "${episode.title}" on Gab TV`)= episode.title - .uk-text-small Posted: #{moment(episode.date_modified).format("MMM DD YYYY HH:MM a")} - mixin renderPageSidebar ( ) //- Gab TV 3 Most Recent Episodes if gabTvChannel @@ -38,4 +26,14 @@ mixin renderPageSidebar ( ) input(id="email", name="email", type="email", placeholder="johnsmith@example.com").uk-input .uk-card-footer - button(type="submit").uk-button.dtp-button-primary.uk-button-small Sign Up (Coming Soon!) \ No newline at end of file + button(type="submit").uk-button.dtp-button-primary.uk-button-small Sign Up (Coming Soon!) + + .uk-margin + + +renderSectionTitle('Widget', { + label: 'Sample URL', + title: 'Sample URL Title', + url: 'https://shing.tv', + }) + + p This would be your widget content if your page even has a sidebar and that sidebar has widgets. \ No newline at end of file diff --git a/app/views/components/pwa-support.pug b/app/views/components/pwa-support.pug index 03d13b62..620d6eae 100644 --- a/app/views/components/pwa-support.pug +++ b/app/views/components/pwa-support.pug @@ -1,20 +1,20 @@ -link(rel="apple-touch-icon" sizes="57x57" href=`/img/icon/icon-57x57.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="60x60" href=`/img/icon/icon-60x60.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="72x72" href=`/img/icon/icon-72x72.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="76x76" href=`/img/icon/icon-76x76.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="114x114" href=`/img/icon/icon-114x114.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="120x120" href=`/img/icon/icon-120x120.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="144x144" href=`/img/icon/icon-144x144.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="152x152" href=`/img/icon/icon-152x152.png?v=${pkg.version}`) -link(rel="apple-touch-icon" sizes="180x180" href=`/img/icon/icon-180x180.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="32x32" href=`/img/icon/icon-32x32.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="96x96" href=`/img/icon/icon-96x96.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="16x16" href=`/img/icon/icon-16x16.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="512x512" href=`/img/icon/icon-512x512.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="384x384" href=`/img/icon/icon-384x384.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="256x256" href=`/img/icon/icon-512x512.png?v=${pkg.version}`) -link(rel="icon" type="image/png" sizes="192x192" href=`/img/icon/icon-192x192.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="57x57" href=`/img/icon/${site.domainKey}/icon-57x57.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="60x60" href=`/img/icon/${site.domainKey}/icon-60x60.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="72x72" href=`/img/icon/${site.domainKey}/icon-72x72.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="76x76" href=`/img/icon/${site.domainKey}/icon-76x76.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="114x114" href=`/img/icon/${site.domainKey}/icon-114x114.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="120x120" href=`/img/icon/${site.domainKey}/icon-120x120.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="144x144" href=`/img/icon/${site.domainKey}/icon-144x144.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="152x152" href=`/img/icon/${site.domainKey}/icon-152x152.png?v=${pkg.version}`) +link(rel="apple-touch-icon" sizes="180x180" href=`/img/icon/${site.domainKey}/icon-180x180.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="32x32" href=`/img/icon/${site.domainKey}/icon-32x32.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="96x96" href=`/img/icon/${site.domainKey}/icon-96x96.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="16x16" href=`/img/icon/${site.domainKey}/icon-16x16.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="512x512" href=`/img/icon/${site.domainKey}/icon-512x512.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="384x384" href=`/img/icon/${site.domainKey}/icon-384x384.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="256x256" href=`/img/icon/${site.domainKey}/icon-512x512.png?v=${pkg.version}`) +link(rel="icon" type="image/png" sizes="192x192" href=`/img/icon/${site.domainKey}/icon-192x192.png?v=${pkg.version}`) link(rel="manifest" href=`/manifest.json?v=${pkg.version}`) meta(name="msapplication-TileColor" content="#f1c52f") -meta(name="msapplication-TileImage" content=`/img/icon/ms-icon-144x144.png?v=${pkg.version}`) +meta(name="msapplication-TileImage" content=`/img/icon/${site.domainKey}/ms-icon-144x144.png?v=${pkg.version}`) meta(name="theme-color" content="#f1c52f") \ No newline at end of file diff --git a/app/views/email/verify-success.pug b/app/views/email/verify-success.pug new file mode 100644 index 00000000..f6e97783 --- /dev/null +++ b/app/views/email/verify-success.pug @@ -0,0 +1,12 @@ +extends ../layouts/main +block content + + section.uk-section.uk-section-default.uk-section-small + .uk-container + h1 Email Verification + p You have successfully verified your email address, and have unlocked additional features of the site. + + a(href="/").uk-button.dtp-button-primary + span + i.fas.fa-home + span.uk-margin-small-left Home \ No newline at end of file diff --git a/app/views/index.pug b/app/views/index.pug index a717ce97..0e4b3a38 100644 --- a/app/views/index.pug +++ b/app/views/index.pug @@ -1,36 +1,8 @@ extends layouts/main-sidebar block content - include components/page-sidebar - include post/components/list-item - include post/components/featured-item + h1 Sample DTP Web Application + p + img(src="/img/the-bobs.jpg", alt="The Bobs have questions").uk-width-large - if site.featuredEmbed - .uk-margin!= site.featuredEmbed - - if Array.isArray(featuredPosts) && (featuredPosts.length > 0) - +renderSectionTitle('Featured Posts') - - - var topPost = featuredPosts.shift(); - .uk-margin - +renderBlogPostFeaturedItem(topPost) - - if (featuredPosts.length > 0) - .uk-margin - div(uk-grid).uk-grid-small - each post in featuredPosts - .uk-width-1-2 - +renderBlogPostFeaturedItem(post) - - //- Blog Posts - +renderSectionTitle('Blog Posts') - - if Array.isArray(posts) && (posts.length > 0) - - var postIndex = 1; - ul.uk-list.uk-list-divider.uk-list-small - each post in posts - li - +renderBlogPostListItem(post, postIndex, 4) - - postIndex += 1; - else - div There are no posts at this time. Please check back later! \ No newline at end of file + p This application doesn't actually do anything. The Bobs would have questions. \ No newline at end of file diff --git a/app/views/layouts/main.pug b/app/views/layouts/main.pug index a99d95a7..efc7b6b8 100644 --- a/app/views/layouts/main.pug +++ b/app/views/layouts/main.pug @@ -10,7 +10,7 @@ html(lang='en') title= pageTitle ? `${pageTitle} | ${site.name}` : site.name meta(name="robots", content= "index,follow") - meta(name="googlebot", conten= "index,follow") + meta(name="googlebot", content= "index,follow") meta(content="#4a4a4a" name="theme-color") meta(content="black-translucent" name="apple-mobile-web-app-status-bar-style") @@ -20,7 +20,7 @@ html(lang='en') block vendorcss - link(rel='stylesheet', href=`/dist/css/style.css?v=${pkg.version}`) + link(rel='stylesheet', href=`/dist/css/${user ? (user.theme || 'dtp-light') : 'dtp-light'}.css?v=${pkg.version}`) block js script(src=`/uikit/js/uikit.min.js?v=${pkg.version}`) @@ -101,14 +101,14 @@ html(lang='en') dtp.channel = !{JSON.stringify(channel || null)}; if DTP_SCRIPT_DEBUG - script(src=`/dist/js/dtpsites-app.js?v=${pkg.version}`, type="module") + script(src=`/dist/js/dtpweb-app.js?v=${pkg.version}`, type="module") else - script(src=`/dist/js/dtpsites-app.min.js?v=${pkg.version}`, type="module") + script(src=`/dist/js/dtpweb-app.min.js?v=${pkg.version}`, type="module") if user && user.flags.isAdmin if DTP_SCRIPT_DEBUG - script(src=`/dist/js/dtpsites-admin.js?v=${pkg.version}`, type="module") + script(src=`/dist/js/dtpweb-admin.js?v=${pkg.version}`, type="module") else - script(src=`/dist/js/dtpsites-admin.min.js?v=${pkg.version}`, type="module") + script(src=`/dist/js/dtpweb-admin.min.js?v=${pkg.version}`, type="module") block viewjs \ No newline at end of file diff --git a/app/views/newsletter/index.pug b/app/views/newsletter/index.pug deleted file mode 100644 index 7e4cc108..00000000 --- a/app/views/newsletter/index.pug +++ /dev/null @@ -1,15 +0,0 @@ -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. \ No newline at end of file diff --git a/app/views/page/view.pug b/app/views/page/view.pug deleted file mode 100644 index 81b04f63..00000000 --- a/app/views/page/view.pug +++ /dev/null @@ -1,18 +0,0 @@ -extends ../layouts/main-sidebar -block content - - include ../components/page-sidebar - - article(dtp-page-id= page._id) - .uk-margin - div(uk-grid) - .uk-width-expand - h1.article-title - span - i(class=`fas ${page.menu.icon}`) - span.uk-margin-left= page.title - if user && user.flags.isAdmin - .uk-width-auto - a(href=`/admin/page/${page._id}`).uk-button.dtp-button-text EDIT - .uk-margin - != page.content diff --git a/app/views/policy/privacy.md b/app/views/policy/privacy.md index 2997192e..9d185be0 100644 --- a/app/views/policy/privacy.md +++ b/app/views/policy/privacy.md @@ -1,33 +1,3 @@ # Privacy Policy -## What Is Collected? - -Your email address, username, and display name are collected during account creation and retained on file with a hash of your password. The information is stored for as long as you keep your account. If you delete your account, the information is destroyed. - -IP addresses are analyzed to perform a GeoIP lookup to provide "best guess" analytics about where traffic comes from. The system does not store IP addresses, only a number of visits from Pittsburgh, PA, for example. - -## Where is information collected? - -You are asked for personal information during account signup and in your account settings. That information is your email address, username, display name, bio, and password. Your password is not stored by this system. A hash of your password is stored, instead. - -The system automates the collection of page visit statistics. We do not use a 3rd-party service to collect or analyze the data. Your IP address is not collected. Your IP address is not being stored. Aggregate stats based on a best guess of your location using GeoIP is stored. - -Information about you is collected when commenting on a post, and you are publicly attributed as the author of a comment. Your IP address is not collected or stored when commenting. Your IP address is not geolocated when commenting (only when loading the article or page). - -## Why do you collect this information? - -Your email address, username, and password are what define your user account on this website. This information is used to enable you to log in and comment on posts and other events on the site. Your email address, if you opt-in for the Newsletter, is used to deliver the Newsletter. - -Your information may be used to enter you in random drawings for giveaways and other contests. This does not happen automatically, you will be asked if you want to participate in a specific contest when they happen. If you opt-in, then you will be part of that contest. If you don't opt-in, you won't be part of the contest. - -Your information may be used to help prevent fraudulent activities and to help us prevent spam in the comments and other areas of the site. Your information is simply how you prove who you are to us. And, if we like you, we let you in. - -We do not otherwise use your information. Your information is not sold or shared with any 3rd-party entities, and will only be used by this website for the purposes stated here. - -## Your rights over your data - -You retain full rights to your data. At any time, you can close your account and all of your data will be removed (deleted). Your data is only retained by this system as long as you keep your account in good standing. - -## Who do you share information with? - -No one. At all. This website uses the information you provide to provide you with service and nothing else. \ No newline at end of file +Your site's privacy policy goes here. \ No newline at end of file diff --git a/app/views/policy/terms-of-service.md b/app/views/policy/terms-of-service.md index 7150a066..f5ecf878 100644 --- a/app/views/policy/terms-of-service.md +++ b/app/views/policy/terms-of-service.md @@ -1,305 +1,3 @@ # Terms of Service -Last Updated: December 30, 2021. - -PLEASE READ THE FOLLOWING TERMS OF SERVICE AGREEMENT CAREFULLY. BY ACCESSING OR USING OUR WEBSITE (THE "SITE") OR OUR SERVICES, YOU HEREBY AGREE TO BE BOUND BY THESE TERMS AND CONDITIONS AND ALL TERMS INCORPORATED HEREIN BY REFERENCE. IT IS THE RESPONSIBILITY OF YOU, THE USER, CUSTOMER, OR PROSPECTIVE CUSTOMER TO READ THE TERMS AND CONDITIONS BEFORE PROCEEDING TO USE THIS SITE. IF YOU DO NOT EXPRESSLY AGREE TO ALL OF THE TERMS AND CONDITIONS, THEN PLEASE DO NOT ACCESS OR USE OUR SITE OR OUR SERVICES. - -The present terms and conditions (this "Agreement" or "Terms") is a legal agreement between you and Just Joe Radio (hereinafter "JJR"), a company duly organized and validly. This Agreement annuls and voids all previous agreements. - -## OVERVIEW - -The Site (www.justjoeradio.com) is operated by JJR. Throughout the Site, the terms "we", "us" and "our" refer to JJR. JJR offers this Site, including all information, tools and services available from this Site to you, the user, conditioned upon your acceptance of all terms, conditions, policies and notices stated here. - -By visiting our Site and/or purchasing something from us, you engage in our "Service" and agree to be bound by the following terms and conditions, including those additional terms and conditions and policies referenced herein and/or available by hyperlink. These Terms apply to all users of the Site, including without limitation users who are browsers, vendors, customers, merchants, and/or contributors of content. In the event of an inconsistency between this Agreement and any additional terms or policies referenced herein, the provisions of the additional terms or policies shall control. - -Please read these Terms carefully before accessing or using our Site. By accessing or using any part of the Site, you agree to be bound by these Terms. If you do not agree to all the Terms of this Agreement, then you may not access the Site or use any Service. If these Terms are considered an offer, acceptance is expressly limited to these Terms. - -Any new features or tools which are added to the current store shall also be subject to the Terms. You can review the most current version of the Terms at any time on this page. We reserve the right to update, change or replace any part of these Terms by posting updates and/or changes to our Site. It is your responsibility to check this page periodically for changes. Your continued use of or access to the Site following the posting of any changes constitutes acceptance of those changes. - -## SECTION 1 - GENERAL TERMS - -By agreeing to these Terms, you represent that you are at least the age of majority in your state or province of residence, or that you are the age of majority in your state or province of residence and you have given us your consent to allow any of your minor dependents to use this Site. - -You may not use our products or Site for any illegal or unauthorized purpose nor may you, in the use of our products or Site, violate any laws in your jurisdiction (including but not limited to motor vehicle laws). - -You must not transmit any worms or viruses or any code of a destructive nature. - -A breach or violation of any of the Terms will result in an immediate termination of your account and right to use our Service. - -We have the right, but not the obligation, to take any of the following actions in our sole discretion at any time and for any reason without giving you any prior notice: - -1. Restrict, suspend or terminate your access to all or any part of our Site; -2. Change, suspend or discontinue all or any part of our products or Site; -3. Refuse, move, or remove any content that is available on all or any part of our Site; -4. Deactivate or delete your accounts; -5. Establish general practices and limits concerning use of our Site. - -You agree that we will not be liable to you or any third party for taking any of -these actions. - -You understand and agree that our Site may include communications such as service announcements and administrative or legal notices from us. Please note that you cannot opt out of receiving these notices. - -You understand that your content (not including credit card information), may be transferred unencrypted and involve (a) transmissions over various networks; and (b) changes to conform and adapt to technical requirements of connecting networks or devices. Credit card information is always encrypted during transfer over networks. - -You agree not to reproduce, duplicate, copy, sell, resell or exploit any portion of the Site, use of the Site, or access to the Site or any contact on the Site, without express written permission by us. - -You may not modify, publish, transmit, reverse engineer, participate in the transfer or sale, create derivative works, or in any way exploit any of the content, in whole or in part, found on the Site. JJR content is not for resale. Use of the Site does not entitle users to make any unauthorized use of any protected content, and in particular you will not delete or alter any proprietary rights or attribution notices in any content. You will use protected content solely for your personal use, and will make no other use of the content without the express written permission of JJR and the copyright owner. You agree that you do not acquire any ownership rights in any protected content. We do not grant you any licenses, express or implied, to the intellectual property of JJR or our licensors except as expressly authorizedby these Terms. - -## SECTION 2 - CREATING AN ACCOUNT - -Once you create an account with us, you are registered on the JJR Site. The terms "member," "membership," and "account" all refer to this registration as a member on JJR's Site. If you are merely surfing or browsing through the Site and have not yet created an account, your use of the Site is still subject to this Agreement; if you do not agree to this Agreement, do not use the Site. - -When you create an account, you will provide a unique username and email. We will also ask you to create a password. Because any activities that occur under your username or password are your responsibility it is important for you to keep your username and/or password secure. You may not assign or otherwise transfer your account to any other person or entity. You acknowledge that JJR is not responsible for third party access to your account that results from theft or misappropriation of your account. Notify us immediately if you believe that someone has used your username, email, or password without your authorization. - -Furthermore, the registering party hereby acknowledges, understands and agrees to: - -a) furnish factual, correct, current and complete information with regards to yourself as may be requested by the data registration process, and -b) maintain and promptly update your registration and profile information in an effort to maintain accuracy and completeness at all times. - -If anyone knowingly provides any information of a false, untrue, inaccurate or incomplete nature, Just Joe Radio will have sufficient grounds and rights to suspend or terminate the member in violation of this aspect of the Agreement, and as such refuse any and all current or future use of Just Joe Radio Services, or any portion thereof. - -## SECTION 3 - CONDUCT - -As a user or member of the Site, you herein acknowledge, understand and agree that all information, text, software, data, photographs, music, video, messages, tags or any other content, whether it is publicly or privately posted and/or transmitted, is the expressed sole responsibility of the individual from whom the content originated. In short, this means that you are solely responsible for any and all content posted, uploaded, emailed, transmitted or otherwise made available by way of the JJR Services, and as such, we do not guarantee the accuracy, integrity or quality of such content. It is expressly understood that by use of our Services, you may be exposed to content including, but not limited to, any errors or omissions in any content posted, and/or any loss or damage of any kind incurred as a result of the use of any content posted, emailed, transmitted or otherwise made available by JJR. - -Furthermore, you herein agree not to make use of Just Joe Radio's Services for the purpose of: - -a) uploading, posting, emailing, transmitting, or otherwise making available any content that shall be deemed unlawful, harmful, threatening, abusive, harassing, tortious, defamatory, libelous, or invasive of another's privacy; - -b) causing harm to minors in any manner whatsoever; - -c) impersonating any individual or entity, including, but not limited to, any JJR officials, forum leaders, guides or hosts or falsely stating or otherwise misrepresenting any affiliation with an individual or entity; - -d) forging captions, headings or titles or otherwise offering any content that you personally have no right to pursuant to any law nor having any contractual or fiduciary relationship with; - -e) uploading, posting, emailing, transmitting or otherwise offering any such content that may infringe upon any patent, copyright, trademark, or any other proprietary or intellectual rights of any other party; - -f) uploading, posting, emailing, transmitting or otherwise offering any content that you do not personally have any right to offer pursuant to any law or in accordance with any contractual or fiduciary relationship; - -g) uploading, posting, emailing, transmitting, or otherwise offering any unsolicited or unauthorized advertising, promotional flyers, "junk mail," "spam," or any other form of solicitation, except in any such areas that may have been designated for such purpose; - -h) uploading, posting, emailing, transmitting, or otherwise offering any source that may contain a software virus or other computer code, any files and/or programs which have been designed to interfere, destroy and/or limit the operation of any computer software, hardware, or telecommunication equipment; - -i) disrupting the normal flow of communication, or otherwise acting in any manner that would negatively affect other users' ability to participate in any real time interactions; - -j) interfering with or disrupting any Just Joe Radio Services, servers and/or networks that may be connected or related to our website, including, but not limited to, the use of any device software and/or routine to bypass the robot exclusion headers; - -k) intentionally or unintentionally violating any local, state, federal, national or international law, including, but not limited to, rules, guidelines, and/or regulations decreed by the U.S. Securities and Exchange Commission, in addition to any rules of any nation or other securities exchange, that would include without limitation, the New York Stock Exchange, the American Stock Exchange, or the NASDAQ, and any regulations having the force of law; - -l) providing informational support or resources, concealing and/or disguising the character, location, and or source to any organization delegated by the United States government as a "foreign terrorist organization" in accordance to Section 219 of the Immigration Nationality Act; - -m) "stalking" or with the intent to otherwise harass another individual; and/or - -n) collecting or storing of any personal data relating to any other member or user in connection with the prohibited conduct and/or activities which have been set forth in the aforementioned paragraphs. - -Just Joe Radio herein reserves the right to pre-screen, refuse and/or delete any content currently available through our Services. In addition, we reserve the right to remove and/or delete any such content that would violate the Terms or which would otherwise be considered offensive to other visitors, users and/or members. - -Just Joe Radio herein reserves the right to access, preserve and/or disclose member account information and/or content if it is requested to do so by law or in good faith belief that any such action is deemed reasonably necessary for: - -a) compliance with any legal process; - -b) enforcement of the Terms; - -c) responding to any claim that therein contained content is in violation of the rights of any third party; - -d) responding to requests for customer service; or - -e) protecting the rights, property or the personal safety of Just Joe Radio, its visitors, users and members, including the general public. - -Just Joe Radio herein reserves the right to include the use of security components that may permit digital information or material to be protected, and that such use of information and/or material is subject to usage guidelines and regulations established by Just Joe Radio or any other content providers supplying content services to Just Joe Radio. You are hereby prohibited from making any attempt to override or circumvent any of the embedded usage rules in our Services. Furthermore, unauthorized reproduction, publication, distribution, or exhibition of any information or materials supplied by our Services, despite whether done so in whole or in part, is expressly prohibited. - -## SECTION 4 - GLOBAL USE; EXPORT/IMPORT COMPLIANCE - -Due to the global nature of the internet, through the use of our network you hereby agree to comply with all local rules relating to online conduct and that which is considered acceptable content. Uploading, posting and/or transferring of software, technology and other technical data may be subject to the export and import laws of the United States and possibly other countries. Through the use of our network, you thus agree to comply with all applicable export and import laws, statutes and regulations, including, but not limited to, the Export Administration Regulations (http://www.access.gpo.gov/bis/ear/ear_data.html), as well as the -sanctions control program of the United States (http://www.treasury.gov/resource-center/sanctions/Programs/Pages/Programs.aspx). - -Furthermore, you state and pledge that you: - -a) are not on the list of prohibited individuals which may be identified on any government export exclusion report (http://www.bis.doc.gov/complianceandenforcement/liststocheck.htm) nor a member of any other government which may be part of an export-prohibited country identified in applicable export and import laws and regulations; - -b) agree not to transfer any software, technology or any other technical data through the use of our network Services to any export-prohibited country; - -c) agree not to use our website network Services for any military, nuclear, missile, chemical or biological weaponry end uses that would be a violation of the U.S. export laws; and - -d) agree not to post, transfer nor upload any software, technology or any other technical data which would be in violation of the U S. or other applicable export and/or import laws. - -## SECTION 5 - SUBMITTED CONTENT - -Just Joe Radio shall not lay claim to ownership of any content submitted by any visitor, member, or user, nor make such content available for inclusion on our website Services. Therefore, you hereby grant and allow for Just Joe Radio the below listed worldwide, royalty-free and non-exclusive licenses, as applicable: - -a) The content submitted or made available for inclusion on the publicly accessible areas of Just Joe Radio's Sites, the license provided to permit to use, distribute, reproduce, modify, adapt, publicly perform and/or publicly display said Content on our network Services is for the sole purpose of providing and promoting the specific area to which this content was placed and/or made available for viewing. This license shall be available so long as you are a member of Just Joe Radio's sites, and shall terminate at such time when you elect to discontinue your membership. - -b) Photos, audio, video and/or graphics submitted or made available for inclusion on the publicly accessible areas of Just Joe Radio's sites, the license provided to permit to use, distribute, reproduce, modify, adapt, publicly perform and/or publicly display said Content on our network Services are for the sole purpose of providing and promoting the specific area in which this content was placed and/or made available for viewing. This license shall be available so long as you are a member of Just Joe Radio's sites and shall terminate at such time when you elect to discontinue your membership. - -c) For any other content submitted or made available for inclusion on the publicly accessible areas of Just Joe Radio's sites, the continuous, binding and completely sub-licensable license which is meant to permit to use, distribute, reproduce, modify, adapt, publish, translate, publicly perform and/or publicly display said content, whether in whole or in part, and the incorporation of any such Content into other works in any arrangement or medium current used or later developed. - -Those areas which may be deemed "publicly accessible" areas of Just Joe Radio's Sites are those such areas of our network properties which are meant to be available to the general public, and which would include message boards and groups that are openly available to both users and members. However, those areas which are not open to the public, and thus available to members only, would include our mail system and instant messaging. - -### CONTRIBUTIONS TO COMPANY WEBSITE - -Just Joe Radio may provide an area for our user and members to contribute feedback to our website. When you submit ideas, documents, suggestions and/or proposals ("Contributions") to our site, you acknowledge and agree that: - -a) your contributions do not contain any type of confidential or proprietary information; - -b) JJR shall not be liable or under any obligation to ensure or maintain confidentiality, expressed or implied, related to any Contributions; - -c) JJR shall be entitled to make use of and/or disclose any such Contributions in any such manner as they may see fit; - -d) the contributor's Contributions shall automatically become the sole property of JJR; and - -e) JJR is under no obligation to either compensate or provide any form of reimbursement in any manner or nature. - -## SECTION 6 - INDEMNITY - -All users and/or members agree to insure and hold Just Joe Radio, our subsidiaries, affiliates, agents, employees, officers, partners and/or licensors blameless or not liable for any claim or demand, which may include, but is not limited to, reasonable attorney fees made by any third party which may arise from any content a member or user of our Site may submit, post, modify, transmit or otherwise make available through our Services, the use of JJR Services or your connection with these Services, your violations of the Terms of Service and/or your violation of any such rights of another person. - -## SECTION 7 - COMMERCIAL REUSE OF SERVICES - -The member or user herein agrees not to replicate, duplicate, copy, trade, sell, resell nor exploit for any commercial reason any part, use of, or access to JJR's sites. - -## SECTION 8 - MODIFICATIONS - -Just Joe Radio reserves the right at any time it may deem fit, to modify, alter and or discontinue, whether temporarily or permanently, our service, or any part thereof, with or without prior notice. In addition, we shall not be held liable to you or to any third party for any such alteration, modification, suspension and/or discontinuance of our Services, or any part thereof. - -## SECTION 9 - TERMINATION - -As a member of www.justjoeradio.com, you may cancel or terminate your account, associated email address and/or access to our Services by submitting a cancellation or termination request to . - -As a member, you agree that Just Joe Radio may, without any prior written notice, immediately suspend, terminate, discontinue and/or limit your account, any email associated with your account, and access to any of our Services. The cause for such termination, discontinuance, suspension and/or limitation of access shall include, but is not limited to: - -a) any breach or violation of our Terms or any other incorporated agreement, regulation and/or guideline; - -b) by way of requests from law enforcement or any other governmental agencies; - -c) the discontinuance, alteration and/or material modification to our Services, or any part thereof; - -d) unexpected technical or security issues and/or problems; - -e) any extended periods of inactivity; - -f) any engagement by you in any fraudulent or illegal activities; and/or - -g) the nonpayment of any associated fees that may be owed by you in connection with your www.justjoeradio.com account Services. - -Furthermore, you herein agree that any and all terminations, suspensions, discontinuances, and or limitations of access for cause shall be made at our sole discretion and that we shall not be liable to you or any other third party with regards to the termination of your account, associated email address and/or access to any of our Services. - -The termination of your account with www.justjoeradio.com shall include any and/or all of the following: - -a) the removal of any access to all or part of the Services offered within www.justjoeradio.com; - -b) the deletion of your password and any and all related information, files, and any such content that may be associated with or inside your account, or any part thereof; and - -c) the barring of any further use of all or part of our Services. - -## SECTION 10 - LINKS - -Either Just Joe Radio or any third parties may provide links to other websites and/or resources. Thus, you acknowledge and agree that we are not responsible for the availability of any such external sites or resources, and as such, we do not endorse nor are we responsible or liable for any content, products, advertising or any other materials, on or available from such third-party sites or resources. Furthermore, you acknowledge and agree that Just Joe Radio shall not be responsible or liable, directly or indirectly, for any such damage or loss which may be a result of, caused or allegedly to be caused by or in connection with the use of or the reliance on any such content, goods or Services made available on or through any such site or resource. - -## SECTION 11 - PROPRIETARY RIGHTS - -You do hereby acknowledge and agree that Just Joe Radio's Services and any essential software that may be used in connection with our Services ("Software") shall contain proprietary and confidential material that is protected by applicable intellectual property rights and other laws. Furthermore, you herein acknowledge and agree that any Content which may be contained in any advertisements or information presented by and through our Services or by advertisers is protected by copyrights, trademarks, patents or other proprietary rights and laws. Therefore, except for that which is expressly permitted by applicable law or as authorized by Just Joe Radio or such applicable licensor, you agree not to alter, modify, lease, rent, loan, sell, distribute, transmit, broadcast, publicly perform and/or created any plagiaristic works which are based on Just Joe Radio Services (e.g. Content or Software), in whole or part. - -Just Joe Radio hereby grants you a personal, non-transferable and non-exclusive right and/or license to make use of the object code or our Software on a single computer, as long as you do not, and shall not, allow any third party to duplicate, alter, modify, create or plagiarize work from, reverse engineer, reverse assemble or otherwise make an attempt to locate or discern any source code, sell, assign, sublicense, grant a security interest in and/or otherwise transfer any such right in the Software. Furthermore, you do herein agree not to alter or change the Software in any manner, nature or form, and as such, not to use any modified versions of the Software, including and without limitation, for the purpose of obtaining unauthorized access to our Services. Lastly, you also agree not to access or attempt to access our Services through any means other than through the interface which is provided by Just Joe Radio for use in accessing our Services. - -## SECTION 12 - WARRANTY DISCLAIMERS - -YOU HEREIN EXPRESSLY ACKNOWLEDGE AND AGREE THAT: - -a) THE USE OF JUST JOE RADIO SERVICES AND SOFTWARE ARE AT THE SOLE RISK BY YOU. OUR SERVICES AND SOFTWARE SHALL BE PROVIDED ON AN "AS IS" AND/OR "AS AVAILABLE" BASIS. JUST JOE RADIO AND OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND LICENSORS EXPRESSLY DISCLAIM ANY AND ALL WARRANTIES OF ANY KIND WHETHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. - -b) JUST JOE RADIO AND OUR SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND LICENSORS MAKE NO SUCH WARRANTIES THAT (i) JUST JOE RADIO SERVICES OR SOFTWARE WILL MEET YOUR REQUIREMENTS; (ii) JUST JOE RADIO SERVICES OR SOFTWARE SHALL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; (iii) THAT SUCH RESULTS WHICH MAY BE OBTAINED FROM THE USE OF THE JUST JOE RADIO SERVICES OR SOFTWARE WILL BE ACCURATE OR RELIABLE; (iv) QUALITY OF ANY PRODUCTS, SERVICES, ANY INFORMATION OR OTHER MATERIAL WHICH MAY BE PURCHASED OR OBTAINED BY YOU THROUGH OUR SERVICES OR SOFTWARE WILL MEET YOUR EXPECTATIONS; AND (v) THAT ANY SUCH ERRORS CONTAINED IN THE SOFTWARE SHALL BE CORRECTED. - -c) ANY INFORMATION OR MATERIAL DOWNLOADED OR OTHERWISE OBTAINED BY WAY OF JUST JOE RADIO SERVICES OR SOFTWARE SHALL BE ACCESSED BY YOUR SOLE DISCRETION AND SOLE RISK, AND AS SUCH YOU SHALL BE SOLELY RESPONSIBLE FOR AND HEREBY WAIVE ANY AND ALL CLAIMS AND CAUSES OF ACTION WITH RESPECT TO ANY DAMAGE TO YOUR COMPUTER AND/OR INTERNET ACCESS, DOWNLOADING AND/OR DISPLAYING, OR FOR ANY LOSS OF DATA THAT COULD RESULT FROM THE DOWNLOAD OF ANY SUCH INFORMATION OR MATERIAL. - -d) NO ADVICE AND/OR INFORMATION, DESPITE WHETHER WRITTEN OR ORAL, THAT MAY BE OBTAINED BY YOU FROM JUST JOE RADIO OR BY WAY OF OR FROM OUR SERVICES OR SOFTWARE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS. - -e) A SMALL PERCENTAGE OF SOME USERS MAY EXPERIENCE SOME DEGREE OF EPILEPTIC SEIZURE WHEN EXPOSED TO CERTAIN LIGHT PATTERNS OR BACKGROUNDS THAT MAY BE CONTAINED ON A COMPUTER SCREEN OR WHILE USING OUR SERVICES. CERTAIN CONDITIONS MAY INDUCE A PREVIOUSLY UNKNOWN CONDITION OR UNDETECTED EPILEPTIC SYMPTOM IN USERS WHO HAVE SHOWN NO HISTORY OF ANY PRIOR SEIZURE OR EPILEPSY. SHOULD YOU, ANYONE YOU KNOW OR ANYONE IN YOUR FAMILY HAVE AN EPILEPTIC CONDITION, PLEASE CONSULT A PHYSICIAN IF YOU EXPERIENCE ANY OF THE FOLLOWING SYMPTOMS WHILE USING OUR SERVICES: DIZZINESS, ALTERED VISION, EYE OR MUSCLE TWITCHES, LOSS OF AWARENESS, DISORIENTATION, ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS. - -## SECTION 13 - LIMITATION OF LIABILITY - -YOU EXPLICITLY ACKNOWLEDGE, UNDERSTAND AND AGREE THAT JUST JOE RADIO AND OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND LICENSORS SHALL NOT BE LIABLE TO YOU FOR ANY PUNITIVE, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DAMAGES WHICH MAY BE RELATED TO THE LOSS OF ANY PROFITS, GOODWILL, USE, DATA AND/OR OTHER INTANGIBLE LOSSES, EVEN THOUGH WE MAY HAVE BEEN ADVISED OF SUCH POSSIBILITY THAT SAID DAMAGES MAY OCCUR, AND RESULT FROM: - -a) THE USE OR INABILITY TO USE OUR SERVICE; - -b) THE COST OF PROCURING SUBSTITUTE GOODS AND SERVICES; - -c) UNAUTHORIZED ACCESS TO OR THE ALTERATION OF YOUR TRANSMISSIONS AND/OR DATA; - -d) STATEMENTS OR CONDUCT OF ANY SUCH THIRD PARTY ON OUR SERVICE; - -e) AND ANY OTHER MATTER WHICH MAY BE RELATED TO OUR SERVICE. - -## SECTION 14 - RELEASE - -In the event you have a dispute, you agree to release Just Joe Radio (and its officers, directors, employees, agents, parent subsidiaries, affiliates, co-branders, partners and any other third parties) from claims, demands and damages (actual and consequential) of every kind and nature, known and unknown, suspected or unsuspected, disclosed and undisclosed, arising out of or in any way connected to such dispute. - -## SECTION 15 - NOTICE - -Just Joe Radio may furnish you with notices, including those with regards to any changes to the Terms, including but not limited to email, regular mail, MMS or SMS, text messaging, postings on our website Services, or other reasonable means currently known or any which may be herein after developed. Any such notices may not be received if you violate any aspects of the Terms by accessing our Services in an unauthorized manner. Your acceptance of this Agreement constitutes your agreement that you are deemed to have received any and all notices that would have been delivered had you accessed our Services in an authorized manner. - -## SECTION 16 - INTELLECTUAL PROPERTY RIGHTS - -You herein acknowledge, understand and agree that all of the Just Joe Radio trademarks, copyright, trade name, service marks, and other Just Joe Radio logos and any brand features, and/or product and service names are trademarks and as such, are and shall remain the property of Just Joe Radio. You herein agree not to display and/or use in any manner the Just Joe Radio logo or marks without obtaining Just Joe Radio's prior written consent. - -Just Joe Radio will always respect the intellectual property of others, and we ask that all of our users do the same. With regards to appropriate circumstances and at its sole discretion, Just Joe Radio may disable and/or terminate the accounts of any user who violates our Terms and/or infringes the rights of others. If you feel that your work has been duplicated in such a way that would constitute copyright infringement, or if you believe your intellectual property rights have been otherwise violated, you should provide to us the following information: - -a) The electronic or the physical signature of the individual that is authorized on behalf of the owner of the copyright or other intellectual property interest; - -b) A description of the copyrighted work or other intellectual property that you believe has been infringed upon; - -c) A description of the location of the site which you allege has been infringing upon your work; - -d) Your physical address, telephone number, and email address; - -e) A statement, in which you state that the alleged and disputed use of your work is not authorized by the copyright owner, its agents or the law; - -f) And finally, a statement, made under penalty of perjury, that the aforementioned information in your notice is truthful and accurate, and that you are the copyright or intellectual property owner, representative or agent authorized to act on the copyright or intellectual property owner's behalf. - -The Just Joe Radio agent for notice of claims of copyright or other intellectual property infringement can be contacted as follows: - -Email: joe@justjoeradio.com - -## SECTION 17 - ENTIRE AGREEMENT - -This Agreement constitutes the entire agreement between you and Just Joe Radio and shall govern the use of our Services, superseding any prior version of this Agreement between you and us with respect to Just Joe Radio Services. You may also be subject to additional terms and conditions that may apply when you use or purchase certain other Just Joe Radio Services, affiliate Services, third-party content or third-party software. - -## SECTION 18 - CHOICE OF LAW AND FORUM - -It is at the mutual agreement of both you and Just Joe Radio with regard to the Agreement that the relationship between the parties shall be governed by the laws of the state of Texas without regard to its conflict of law provisions and that any and all claims, causes of action and/or disputes, arising out of or relating to the Agreement, or the relationship between you and Just Joe Radio, shall be filed within the courts having jurisdiction within the County of , or the U.S. District Court located in said state. You and Just Joe Radio agree to submit to the jurisdiction of the courts as previously mentioned, and agree to waive any and all objections to the exercise of jurisdiction over the parties by such courts and to venue in such courts. - -## SECTION 19 - WAIVER AND SEVERABILITY OF TERMS - -At any time, should Just Joe Radio fail to exercise or enforce any right or provision of the Agreement, such failure shall not constitute a waiver of such right or provision. If any provision of this Agreement is found by a court of competent jurisdiction to be invalid, the parties nevertheless agree that the court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Agreement remain in full force and effect. - -## SECTION 20 - NO RIGHT OF SURVIVORSHIP NON-TRANSFERABILITY - -You acknowledge, understand and agree that your account is non-transferable and any rights to your ID and/or contents within your account shall terminate upon your death. Upon receipt of a copy of a death certificate, your account may be terminated and all contents therein permanently deleted. - -## SECTION 21 - STATUTE OF LIMITATIONS - -You acknowledge, understand and agree that regardless of any statute or law to the contrary, any claim or action arising out of or related to the use of our Services or the Agreement must be filed within year(s) after said claim or cause of action arose or shall be forever barred. - -## SECTION 22 - VIOLATIONS - -Please report any and all violations of this Agreement to Just Joe Radio as follows: - -Email: -joe@justjoeradio.com - -## SECTION 23 - GOVERNMENT REQUESTS - -In order to cooperate with governmental requests, subpoenas or court orders, to protect our systems, or to ensure the integrity and operation of our business and systems, we may access and disclose any information we consider necessary or appropriate, including and without limitation, your information, IP address, and usage history. Our right to disclose any such information is governed by the terms of our Privacy Policy. - -## SECTION 24 - FOREIGN ACCESS OF SITE - -The Site is controlled, operated and administered by JJR from our offices within the USA. If you access the Site from a location outside the USA, you are responsible for compliance with all local laws. You agree that you will not use JJR's content accessed through the Site in any country or in any manner prohibited by any applicable laws, restrictions or regulations. - -## SECTION 25 - ERRORS, INACCURACIES AND OMISSIONS - -Occasionally there may be information on our Site that contains typographical errors, inaccuracies or omissions that may relate to product descriptions, pricing, promotions, offers, product shipping charges, transit times and availability. We reserve the right to correct any errors, inaccuracies or omissions, and to change or update information or cancel orders if any information on the Site or on any related Site is inaccurate at any time without prior notice (including after you have submitted your order). - -We undertake no obligation to update, amend or clarify information on the Site or on any related Site, including without limitation, pricing information, except as required by law. No specified update or refresh date applied on the Site or on any related Site, should be taken to indicate that all information on the Site or on any related Site has been modified or updated. - -## SECTION 26 - PRIVACY POLICY - -Every member's registration data and various other personal information are strictly protected by the Just Joe Radio Online Privacy Policy (see the full Privacy Policy at [https://justjoeradio.com/policy/privacy](https://justjoeradio.com/policy/privacy)). As a member, you herein consent to the collection and use of the information provided, including the transfer of information within the United States and/or other countries for storage, processing or use by Just Joe Radio and/or our subsidiaries and affiliates. \ No newline at end of file +Your site's Terms of Service go here. \ No newline at end of file diff --git a/app/views/post/components/featured-item.pug b/app/views/post/components/featured-item.pug deleted file mode 100644 index 73db7e2b..00000000 --- a/app/views/post/components/featured-item.pug +++ /dev/null @@ -1,15 +0,0 @@ -mixin renderBlogPostFeaturedItem (post) - a(href=`/post/${post.slug}`).uk-display-block.uk-link-reset - div(class='uk-visible@m').uk-margin-small - if post.image - img(src= `/image/${post.image._id}`).responsive - else - img(src="/img/default-poster.jpg").responsive - - article.uk-article - h4(style="line-height: 1.1;").uk-article-title.uk-margin-small= post.title - .uk-article-meta - if post.updated - span updated: #{moment(post.updated).format("MMM DD YYYY HH:MM a")} - else - span published: #{moment(post.created).format("MMM DD YYYY HH:MM a")} \ No newline at end of file diff --git a/app/views/post/components/list-item.pug b/app/views/post/components/list-item.pug deleted file mode 100644 index 02bbce81..00000000 --- a/app/views/post/components/list-item.pug +++ /dev/null @@ -1,24 +0,0 @@ -mixin renderBlogPostListItem (post, postIndex = 1, postIndexModulus = 3) - a(href=`/post/${post.slug}`).uk-display-block.uk-link-reset - div(uk-grid).uk-grid-small - - div(class='uk-visible@m', class={ - 'uk-flex-first': ((postIndex % postIndexModulus) === 0), - 'uk-flex-last': ((postIndex % postIndexModulus) !== 0), - }).uk-width-1-3 - if post.image - img(src= `/image/${post.image._id}`).responsive - else - img(src="/img/default-poster.jpg").responsive - - div(class='uk-width-1-1 uk-width-2-3@m', class={ - 'uk-flex-first': ((postIndex % postIndexModulus) !== 0), - 'uk-flex-last': ((postIndex % postIndexModulus) === 0), - }) - article.uk-article - h4(style="line-height: 1.1;").uk-article-title.uk-margin-small= post.title - .uk-article-meta - if post.updated - span updated: #{moment(post.updated).format("MMM DD YYYY HH:MM a")} - else - span published: #{moment(post.created).format("MMM DD YYYY HH:MM a")} \ No newline at end of file diff --git a/app/views/post/view.pug b/app/views/post/view.pug deleted file mode 100644 index 337995da..00000000 --- a/app/views/post/view.pug +++ /dev/null @@ -1,66 +0,0 @@ -extends ../layouts/main-sidebar -block content - - include ../comment/components/comment-list - include ../comment/components/composer - - article(dtp-post-id= post._id) - .uk-margin - h1.article-title= post.title - .uk-text-lead= post.summary - .uk-margin - .uk-article-meta - div(uk-grid).uk-grid-small.uk-flex-top - .uk-width-expand - div #{moment(post.created).format('MMM DD, YYYY, hh:mm a')}, by #[a(href=`/user/${post.author._id}`)= post.author.displayName || post.author.username] - if user && user.flags.isAdmin - .uk-width-auto - a(href=`/admin/post/${post._id}`) - +renderButtonIcon('fa-pen', 'edit') - .uk-width-auto - +renderButtonIcon('fa-eye', displayIntegerValue(post.stats.totalViewCount)) - .uk-width-auto - +renderButtonIcon('fa-chevron-up', displayIntegerValue(post.stats.upvoteCount)) - .uk-width-auto - +renderButtonIcon('fa-chevron-down', displayIntegerValue(post.stats.downvoteCount)) - .uk-width-auto - +renderButtonIcon('fa-comment', displayIntegerValue(post.stats.commentCount)) - .uk-margin - != post.content - - if post.updated - .uk-margin - .uk-article-meta This post was updated on #{moment(post.updated).format('MMMM DD, YYYY, [at] hh:mm a')}. - - .uk-margin - h4 Post author - div(uk-grid).uk-grid-small - if post.author.picture && post.author.picture.small - .uk-width-auto - img(src=`/image/${post.author.picture.small}`) - .uk-width-expand - .uk-text-bold= post.author.displayName || post.author.username - .uk-text-small= post.author.bio - - if user && post.flags.enableComments && user.permissions.canComment - +renderSectionTitle('Add a comment') - - .uk-margin - +renderCommentComposer(`/post/${post._id}/comment`) - - if featuredComment - #featured-comment.uk-margin-large - .uk-margin - +renderSectionTitle('Linked Comment') - +renderComment(featuredComment) - - .uk-margin - +renderSectionTitle('Comments') - - .uk-margin - if Array.isArray(comments) && (comments.length > 0) - ul#post-comment-list.uk-list.uk-list-divider.uk-list-large - +renderCommentList(comments, { countPerPage, rootUrl: `/post/${post.slug}/comment` }) - else - ul#post-comment-list.uk-list.uk-list-divider.uk-list-large - div There are no comments at this time. Please check back later. \ No newline at end of file diff --git a/app/views/user/otp-disabled.pug b/app/views/user/otp-disabled.pug new file mode 100644 index 00000000..41d7989a --- /dev/null +++ b/app/views/user/otp-disabled.pug @@ -0,0 +1,8 @@ +extends ../layouts/main +block content + + section.uk-section.uk-section-default + .uk-container.uk-text-center + h1 Two-Factor Authentication + p You have successfully disabled Two-Factor Authentication. + a(href=`/user/${user._id}/settings?tab=account`).uk-button.dtp-button-default Return to Settings \ No newline at end of file diff --git a/app/views/user/otp-setup-complete.pug b/app/views/user/otp-setup-complete.pug new file mode 100644 index 00000000..294483c1 --- /dev/null +++ b/app/views/user/otp-setup-complete.pug @@ -0,0 +1,8 @@ +extends ../layouts/main +block content + + section.uk-section.uk-section-default + .uk-container.uk-text-center + h1 Two-Factor Authentication + p You have successfully enabled Two-Factor Authentication on your account. + a(href=`/user/${user._id}/settings?tab=account`).uk-button.dtp-button-default Return to Settings \ No newline at end of file diff --git a/app/views/user/settings.pug b/app/views/user/settings.pug index 918d4fbb..67c92816 100644 --- a/app/views/user/settings.pug +++ b/app/views/user/settings.pug @@ -7,10 +7,8 @@ block content include ../components/file-upload-image - section.uk-section.uk-section-default + section.uk-section.uk-section-default.uk-section-small .uk-container - h1 Settings - div(uk-grid) div(class="uk-width-1-1 uk-width-1-3@m") - @@ -19,17 +17,34 @@ block content currentProfile = user.picture.large; } .uk-margin - +renderFileUploadImage( - `/user/${user._id}/profile-photo`, - 'profile-picture-upload', - 'profile-picture-file', - 'site-profile-picture', - `/img/default-member.png`, - currentProfile, - { aspectRatio: 1 }, - ) + .uk-card.uk-card-default.uk-card-small + .uk-card-header.uk-text-center + h1.uk-card-title Profile Photo + .uk-card-body + +renderFileUploadImage( + `/user/${user._id}/profile-photo`, + 'profile-picture-upload', + 'profile-picture-file', + 'site-profile-picture', + `/img/default-member.png`, + currentProfile, + { aspectRatio: 1 }, + ) + + .uk-margin + .uk-card.uk-card-default.uk-card-small + .uk-card-header.uk-text-center + h1.uk-card-title Two-Factor Authentication + .uk-card-body + p Enabling Two-Factor Authentication (2FA) with a One-Time Password authenticator app can help protect your account and settings from unauthorized access. + .uk-card-footer.uk-text-center + if hasOtpAccount + a(href=`/user/${user._id}/otp-disable`).uk-button.dtp-button-danger Disable 2FA + else + a(href=`/user/${user._id}/otp-setup`).uk-button.dtp-button-default Enable 2FA div(class="uk-width-1-1 uk-width-expand@m") + h1 Settings form(method="POST", action=`/user/${user._id}/settings`, onsubmit="return dtp.app.submitForm(event, 'user account update');").uk-form .uk-margin label(for="username").uk-form-label Username @@ -37,9 +52,51 @@ block content .uk-margin label(for="display-name").uk-form-label Display Name input(id="display-name", name="displayName", type="text", placeholder="Enter display name", value= user.displayName).uk-input + + .uk-margin + div(uk-grid).uk-grid-small + .uk-width-1-2 + .uk-margin + label(for="password").uk-form-label New Password + input(id="password", name="password", type="password", placeholder="Enter new password", autocomplete= "new-password").uk-input + .uk-width-1-2 + .uk-margin + label(for="passwordv").uk-form-label Verify New Password + input(id="passwordv", name="passwordv", type="password", placeholder="Enter new password again", autocomplete= "new-password").uk-input + + fieldset + legend Email Preferences + + .uk-margin + label(for="email").uk-form-label + span Email Address + if user.flags.isEmailVerified + span (verified) + div(uk-grid).uk-grid-small + div(class="uk-width-1-1 uk-width-expand@s") + .uk-margin-small + input(id="email", name="email", type="email", placeholder="Enter email address", value= user.email).uk-input + if user.flags.isEmailVerified + .uk-text-small.uk-text-muted Changing your email address will un-verify you and send a new verification email. Check your spam folder! + else + .uk-text-small.uk-text-muted Changing your email address will send a new verification email. Check your spam folder! + div(class="uk-width-1-1 uk-width-auto@s") + button(type="button", onclick="return dtp.app.resendWelcomeEmail(event);").uk-button.dtp-button-default Resend Welcome Email + + .uk-margin + div(uk-grid).uk-grid-small + label + input(id="optin-system", name="optIn.system", type="checkbox", checked= user.optIn ? user.optIn.system : false).uk-checkbox + | System messages + label + input(id="optin-marketing", name="optIn.marketing", type="checkbox", checked= user.optIn ? user.optIn.marketing : false).uk-checkbox + | Sales / Marketing + .uk-margin - label(for="bio").uk-form-label Profile bio - textarea(id="bio", name="bio", rows="3", placeholder="Tell people about yourself").uk-textarea.uk-resize-vertival= user.bio + label(for="theme").uk-form-label UI Theme + select(id="theme", name="theme").uk-select + option(value="dtp-light", selected= user ? user.theme === 'dtp-light' : true) Light Mode + option(value="dtp-dark", selected= user ? user.theme === 'dtp-dark' : false) Dark Mode .uk-margin - button(type="submit").uk-button.dtp-button-primary Update account settings + button(type="submit").uk-button.dtp-button-primary Update account settings \ No newline at end of file diff --git a/app/views/welcome/index.pug b/app/views/welcome/index.pug index 282a8150..e5251940 100644 --- a/app/views/welcome/index.pug +++ b/app/views/welcome/index.pug @@ -4,7 +4,7 @@ block content section.uk-section.uk-section-secondary .uk-container.uk-text-center .uk-width-auto.uk-margin-auto - img(src="/img/icon/icon-256x256.png") + img(src=`/img/icon/${site.domainKey}/icon-256x256.png`).uk-border-rounded h1= site.name .uk-text-lead= site.description @@ -14,4 +14,4 @@ block content .uk-width-auto a(href="/welcome/signup").uk-button.dtp-button-primary Create Account .uk-width-auto - a(href="/welcome/login").uk-button.dtp-button-secondary Sign In + a(href="/welcome/login").uk-button.dtp-button-secondary Sign In \ No newline at end of file diff --git a/app/views/welcome/login.pug b/app/views/welcome/login.pug index bec2a155..aab8ff1e 100644 --- a/app/views/welcome/login.pug +++ b/app/views/welcome/login.pug @@ -12,7 +12,7 @@ block content .uk-margin-small div(uk-grid) .uk-width-1-3 - img(src=`/img/icon/${site.domainKey}.png`).responsive + img(src=`/img/icon/${site.domainKey}/icon-256x256.png`).responsive.uk-border-rounded .uk-width-expand if loginResult div(uk-alert).uk-alert.uk-alert-danger= loginResult diff --git a/app/views/welcome/signup.pug b/app/views/welcome/signup.pug index 77e3789c..1f33b812 100644 --- a/app/views/welcome/signup.pug +++ b/app/views/welcome/signup.pug @@ -35,11 +35,6 @@ block content input(id="passwordv", name="passwordv", type="password", placeholder="Verify password").uk-input .uk-text-small.uk-text-muted.uk-margin-small-top(class="uk-visible@m") Please enter your password again to prove you're not an idiot. - .uk-margin - label - input(type="checkbox", checked).uk-checkbox - | Join #{site.name}'s Newsletter - section.uk-section.uk-section-secondary.uk-section .uk-container.uk-container-small .uk-margin-large diff --git a/app/workers/host-services.js b/app/workers/host-services.js index 19df2549..99cb011c 100644 --- a/app/workers/host-services.js +++ b/app/workers/host-services.js @@ -1,5 +1,5 @@ // host-services.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -585,6 +585,18 @@ module.expireNetHosts = async ( ) => { (async ( ) => { try { + process.on('unhandledRejection', (error, p) => { + module.log.error('Unhandled rejection', { + error: error, + promise: p, + stack: error.stack + }); + }); + + process.on('warning', (error) => { + module.log.alert('warning', { error }); + }); + process.once('SIGINT', async ( ) => { module.log.info('SIGINT received'); module.log.info('requesting shutdown...'); diff --git a/app/workers/newsletter.js b/app/workers/newsletter.js deleted file mode 100644 index 6fc3ee63..00000000 --- a/app/workers/newsletter.js +++ /dev/null @@ -1,113 +0,0 @@ -// newsletter.js -// Copyright (C) 2021 Digital Telepresence, LLC -// License: Apache-2.0 - -'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: 'demo@wherever.com', - 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); - } -})(); \ No newline at end of file diff --git a/app/workers/reeeper.js b/app/workers/reeeper.js index cb4a91b4..dacf7f9d 100644 --- a/app/workers/reeeper.js +++ b/app/workers/reeeper.js @@ -1,5 +1,5 @@ // host-services.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -46,11 +46,23 @@ module.expireCrashedHosts = async ( ) => { (async ( ) => { try { + process.on('unhandledRejection', (error, p) => { + module.log.error('Unhandled rejection', { + error: error, + promise: p, + stack: error.stack + }); + }); + + process.on('warning', (error) => { + module.log.alert('warning', { error }); + }); + process.once('SIGINT', async ( ) => { module.log.info('SIGINT received'); module.log.info('requesting shutdown...'); - const exitCode = await SitePlatform.shutdown(); + process.nextTick(( ) => { process.exit(exitCode); }); diff --git a/client/img/altmedia.png b/client/img/altmedia.png deleted file mode 100644 index b00f2f56..00000000 Binary files a/client/img/altmedia.png and /dev/null differ diff --git a/client/img/default-poster.jpg b/client/img/default-poster.jpg deleted file mode 100644 index 919d8ab6..00000000 Binary files a/client/img/default-poster.jpg and /dev/null differ diff --git a/client/img/donate-qr-init.png b/client/img/donate-qr-init.png deleted file mode 100644 index 4ec7b4b4..00000000 Binary files a/client/img/donate-qr-init.png and /dev/null differ diff --git a/client/img/gab-g.svg b/client/img/gab-g.svg deleted file mode 100644 index 4acec363..00000000 --- a/client/img/gab-g.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/client/img/header-sample.png b/client/img/header-sample.png deleted file mode 100644 index abec8966..00000000 Binary files a/client/img/header-sample.png and /dev/null differ diff --git a/client/img/icon/icon-114x114.png b/client/img/icon/icon-114x114.png deleted file mode 100644 index cdff4279..00000000 Binary files a/client/img/icon/icon-114x114.png and /dev/null differ diff --git a/client/img/icon/icon-120x120.png b/client/img/icon/icon-120x120.png deleted file mode 100644 index 8522d1f7..00000000 Binary files a/client/img/icon/icon-120x120.png and /dev/null differ diff --git a/client/img/icon/icon-144x144.png b/client/img/icon/icon-144x144.png deleted file mode 100644 index 637e04e8..00000000 Binary files a/client/img/icon/icon-144x144.png and /dev/null differ diff --git a/client/img/icon/icon-150x150.png b/client/img/icon/icon-150x150.png deleted file mode 100644 index 99635d23..00000000 Binary files a/client/img/icon/icon-150x150.png and /dev/null differ diff --git a/client/img/icon/icon-152x152.png b/client/img/icon/icon-152x152.png deleted file mode 100644 index 5b809386..00000000 Binary files a/client/img/icon/icon-152x152.png and /dev/null differ diff --git a/client/img/icon/icon-16x16.png b/client/img/icon/icon-16x16.png deleted file mode 100644 index 4cd8efc6..00000000 Binary files a/client/img/icon/icon-16x16.png and /dev/null differ diff --git a/client/img/icon/icon-180x180.png b/client/img/icon/icon-180x180.png deleted file mode 100644 index 7acf658e..00000000 Binary files a/client/img/icon/icon-180x180.png and /dev/null differ diff --git a/client/img/icon/icon-192x192.png b/client/img/icon/icon-192x192.png deleted file mode 100644 index 4ff9d8eb..00000000 Binary files a/client/img/icon/icon-192x192.png and /dev/null differ diff --git a/client/img/icon/icon-256x256.png b/client/img/icon/icon-256x256.png deleted file mode 100644 index f8d2d7f5..00000000 Binary files a/client/img/icon/icon-256x256.png and /dev/null differ diff --git a/client/img/icon/icon-310x310.png b/client/img/icon/icon-310x310.png deleted file mode 100644 index b049ab64..00000000 Binary files a/client/img/icon/icon-310x310.png and /dev/null differ diff --git a/client/img/icon/icon-32x32.png b/client/img/icon/icon-32x32.png deleted file mode 100644 index 0cddef8e..00000000 Binary files a/client/img/icon/icon-32x32.png and /dev/null differ diff --git a/client/img/icon/icon-36x36.png b/client/img/icon/icon-36x36.png deleted file mode 100644 index ce2df493..00000000 Binary files a/client/img/icon/icon-36x36.png and /dev/null differ diff --git a/client/img/icon/icon-384x384.png b/client/img/icon/icon-384x384.png deleted file mode 100644 index b812a38d..00000000 Binary files a/client/img/icon/icon-384x384.png and /dev/null differ diff --git a/client/img/icon/icon-48x48.png b/client/img/icon/icon-48x48.png deleted file mode 100644 index b1eda578..00000000 Binary files a/client/img/icon/icon-48x48.png and /dev/null differ diff --git a/client/img/icon/icon-512x512.png b/client/img/icon/icon-512x512.png deleted file mode 100644 index b61ab224..00000000 Binary files a/client/img/icon/icon-512x512.png and /dev/null differ diff --git a/client/img/icon/icon-57x57.png b/client/img/icon/icon-57x57.png deleted file mode 100644 index 3df418c9..00000000 Binary files a/client/img/icon/icon-57x57.png and /dev/null differ diff --git a/client/img/icon/icon-60x60.png b/client/img/icon/icon-60x60.png deleted file mode 100644 index 8df806d7..00000000 Binary files a/client/img/icon/icon-60x60.png and /dev/null differ diff --git a/client/img/icon/icon-70x70.png b/client/img/icon/icon-70x70.png deleted file mode 100644 index 09aede8f..00000000 Binary files a/client/img/icon/icon-70x70.png and /dev/null differ diff --git a/client/img/icon/icon-72x72.png b/client/img/icon/icon-72x72.png deleted file mode 100644 index a8c29bf3..00000000 Binary files a/client/img/icon/icon-72x72.png and /dev/null differ diff --git a/client/img/icon/icon-76x76.png b/client/img/icon/icon-76x76.png deleted file mode 100644 index 33d87be7..00000000 Binary files a/client/img/icon/icon-76x76.png and /dev/null differ diff --git a/client/img/icon/icon-96x96.png b/client/img/icon/icon-96x96.png deleted file mode 100644 index 939a540a..00000000 Binary files a/client/img/icon/icon-96x96.png and /dev/null differ diff --git a/client/img/icon/justjoeradio.com.png b/client/img/icon/justjoeradio.com.png deleted file mode 100644 index 34171744..00000000 Binary files a/client/img/icon/justjoeradio.com.png and /dev/null differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/dtp-sites.icon.png b/client/img/icon/webapp.digitaltelepresence.com/dtp-sites.icon.png new file mode 100644 index 00000000..a14a21de Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/dtp-sites.icon.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-114x114.png b/client/img/icon/webapp.digitaltelepresence.com/icon-114x114.png new file mode 100644 index 00000000..d0e6fa07 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-114x114.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-120x120.png b/client/img/icon/webapp.digitaltelepresence.com/icon-120x120.png new file mode 100644 index 00000000..e7bfdee5 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-120x120.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-144x144.png b/client/img/icon/webapp.digitaltelepresence.com/icon-144x144.png new file mode 100644 index 00000000..1a404c0a Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-144x144.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-150x150.png b/client/img/icon/webapp.digitaltelepresence.com/icon-150x150.png new file mode 100644 index 00000000..12fffff7 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-150x150.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-152x152.png b/client/img/icon/webapp.digitaltelepresence.com/icon-152x152.png new file mode 100644 index 00000000..b0fe14ce Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-152x152.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-16x16.png b/client/img/icon/webapp.digitaltelepresence.com/icon-16x16.png new file mode 100644 index 00000000..b9735493 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-16x16.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-180x180.png b/client/img/icon/webapp.digitaltelepresence.com/icon-180x180.png new file mode 100644 index 00000000..7eef324c Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-180x180.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-192x192.png b/client/img/icon/webapp.digitaltelepresence.com/icon-192x192.png new file mode 100644 index 00000000..b6f71c19 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-192x192.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-256x256.png b/client/img/icon/webapp.digitaltelepresence.com/icon-256x256.png new file mode 100644 index 00000000..e1af3a83 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-256x256.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-310x310.png b/client/img/icon/webapp.digitaltelepresence.com/icon-310x310.png new file mode 100644 index 00000000..0ce8b601 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-310x310.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-32x32.png b/client/img/icon/webapp.digitaltelepresence.com/icon-32x32.png new file mode 100644 index 00000000..8fc4c7f6 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-32x32.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-36x36.png b/client/img/icon/webapp.digitaltelepresence.com/icon-36x36.png new file mode 100644 index 00000000..ff9ad7ba Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-36x36.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-384x384.png b/client/img/icon/webapp.digitaltelepresence.com/icon-384x384.png new file mode 100644 index 00000000..3e513995 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-384x384.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-48x48.png b/client/img/icon/webapp.digitaltelepresence.com/icon-48x48.png new file mode 100644 index 00000000..93113705 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-48x48.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-512x512.png b/client/img/icon/webapp.digitaltelepresence.com/icon-512x512.png new file mode 100644 index 00000000..7951cf24 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-512x512.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-57x57.png b/client/img/icon/webapp.digitaltelepresence.com/icon-57x57.png new file mode 100644 index 00000000..c93bb083 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-57x57.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-60x60.png b/client/img/icon/webapp.digitaltelepresence.com/icon-60x60.png new file mode 100644 index 00000000..9d407afa Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-60x60.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-70x70.png b/client/img/icon/webapp.digitaltelepresence.com/icon-70x70.png new file mode 100644 index 00000000..2b5189d8 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-70x70.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-72x72.png b/client/img/icon/webapp.digitaltelepresence.com/icon-72x72.png new file mode 100644 index 00000000..6e597608 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-72x72.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-76x76.png b/client/img/icon/webapp.digitaltelepresence.com/icon-76x76.png new file mode 100644 index 00000000..cce00f89 Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-76x76.png differ diff --git a/client/img/icon/webapp.digitaltelepresence.com/icon-96x96.png b/client/img/icon/webapp.digitaltelepresence.com/icon-96x96.png new file mode 100644 index 00000000..28efcfac Binary files /dev/null and b/client/img/icon/webapp.digitaltelepresence.com/icon-96x96.png differ diff --git a/client/img/logo.png b/client/img/logo.png deleted file mode 100644 index aee9756e..00000000 Binary files a/client/img/logo.png and /dev/null differ diff --git a/client/img/lra-logo.png b/client/img/lra-logo.png deleted file mode 100644 index b87a4dda..00000000 Binary files a/client/img/lra-logo.png and /dev/null differ diff --git a/client/img/the-bobs.jpg b/client/img/the-bobs.jpg new file mode 100644 index 00000000..83282622 Binary files /dev/null and b/client/img/the-bobs.jpg differ diff --git a/client/js/index-admin.js b/client/js/index-admin.js index fe110ed9..7914cefa 100644 --- a/client/js/index-admin.js +++ b/client/js/index-admin.js @@ -1,5 +1,5 @@ // index-admin.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -9,14 +9,14 @@ const DTP_COMPONENT_NAME = 'SiteAdmin'; const dtp = window.dtp = window.dtp || { }; dtp.admin = dtp.admin || { }; -import DtpSiteAdminHostStatsApp from './site-admin-host-stats-app.js'; +import DtpSiteAdminApp from './site-admin-app.js'; import DtpWebLog from 'dtp/dtp-log.js'; window.addEventListener('load', async ( ) => { // application console log dtp.admin.log = new DtpWebLog(DTP_COMPONENT_NAME); - dtp.adminApp = new DtpSiteAdminHostStatsApp(dtp.user); + dtp.adminApp = new DtpSiteAdminApp(dtp.user); dtp.admin.log.debug('load', 'dispatching load event'); window.dispatchEvent(new Event('dtp-load-admin')); diff --git a/client/js/index.js b/client/js/index.js index e9908123..a053a127 100644 --- a/client/js/index.js +++ b/client/js/index.js @@ -1,5 +1,5 @@ // index.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/client/js/site-admin-host-stats-app.js b/client/js/site-admin-app.js similarity index 86% rename from client/js/site-admin-host-stats-app.js rename to client/js/site-admin-app.js index 633f1197..d42c74a2 100644 --- a/client/js/site-admin-host-stats-app.js +++ b/client/js/site-admin-app.js @@ -1,5 +1,5 @@ // dtp-site-app.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -202,6 +202,43 @@ export default class DtpSiteAdminHostStatsApp extends DtpApp { }); } + async jobQueueAction (event) { + event.preventDefault(); + event.stopPropagation(); + + const target = event.currentTarget || event.target; + const queueName = target.getAttribute('data-job-queue'); + const jobId = target.getAttribute('data-job-id'); + const action = target.getAttribute('data-job-action'); + + try { + this.log.info('queueJobRemove', 'removing job from queue', { queueName, jobId }); + + const response = await fetch(`/admin/job-queue/${queueName}/${jobId}/action`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ action }), + }); + if (!response.ok) { + throw new Error('Server error'); + } + + const json = await response.json(); + if (!json.success) { + throw new Error(json.message); + } + + if (['remove','discard'].includes(action)) { + window.location = `/admin/job-queue/${queueName}`; + } + } catch (error) { + UIkit.modal.alert(`Failed to remove job: ${error.message}`); + } + } + + async deleteNewsletter (event) { const newsletterId = event.currentTarget.getAttribute('data-newsletter-id'); const newsletterTitle = event.currentTarget.getAttribute('data-newsletter-title'); diff --git a/client/js/site-app.js b/client/js/site-app.js index 0115d67b..51f3cd4e 100644 --- a/client/js/site-app.js +++ b/client/js/site-app.js @@ -1,5 +1,5 @@ // site-app.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/client/less/dtp-dark.less b/client/less/dtp-dark.less new file mode 100644 index 00000000..35dd0cbf --- /dev/null +++ b/client/less/dtp-dark.less @@ -0,0 +1,6 @@ +@import "site/variables.less"; + +@import "node_modules/uikit/src/less/uikit.theme.less"; +@import "site/uikit-theme.dtp-dark.less"; + +@import "style.common.less"; \ No newline at end of file diff --git a/client/less/dtp-light.less b/client/less/dtp-light.less new file mode 100644 index 00000000..182e35c3 --- /dev/null +++ b/client/less/dtp-light.less @@ -0,0 +1,6 @@ +@import "site/variables.less"; + +@import "node_modules/uikit/src/less/uikit.theme.less"; +@import "site/uikit-theme.dtp-light.less"; + +@import "style.common.less"; \ No newline at end of file diff --git a/client/less/site/header-section.less b/client/less/site/header-section.less index fe2ef779..2d84c173 100644 --- a/client/less/site/header-section.less +++ b/client/less/site/header-section.less @@ -1,6 +1,5 @@ .uk-section.uk-section-header { - background-color: @site-brand-color; - color: white; + color: @site-brand-color; .profile-header-name { font-size: 2.5em; @@ -20,5 +19,5 @@ .uk-section.uk-section-header h4, .uk-section.uk-section-header h5, .uk-section.uk-section-header h6 { - color: white; + color: @site-brand-color; } \ No newline at end of file diff --git a/client/less/site/image.less b/client/less/site/image.less index 1faa6e58..509549e1 100644 --- a/client/less/site/image.less +++ b/client/less/site/image.less @@ -25,7 +25,7 @@ img.site-profile-picture { height: auto; margin-left: auto; margin-right: auto; - border-radius: 50%; + border-radius: 5%; background-color: #8a8a8a; } diff --git a/client/less/site/site-home.less b/client/less/site/site-home.less deleted file mode 100644 index e69de29b..00000000 diff --git a/client/less/site/uikit-theme.less b/client/less/site/uikit-theme.dtp-dark.less similarity index 74% rename from client/less/site/uikit-theme.less rename to client/less/site/uikit-theme.dtp-dark.less index b4cbfd2c..afe72405 100644 --- a/client/less/site/uikit-theme.less +++ b/client/less/site/uikit-theme.dtp-dark.less @@ -2,6 +2,12 @@ // Colors // +@site-brand-color: #ff0013; +@button-label-color: #e8e8e8; +@button-label-hover: #2a2a2a; +@social-link-color: #e8e8e8; +@checkout-button-text-color: #e8e8e8; + @global-background: #1a1a1a; @global-muted-background: #3a3a3a; @global-primary-background: #1e87f0; @@ -82,8 +88,8 @@ // Form // -@internal-form-select-image: "../images/backgrounds/form-select.svg"; -@internal-form-datalist-image: "../images/backgrounds/form-datalist.svg"; -@internal-form-radio-image: "../images/backgrounds/form-radio.svg"; -@internal-form-checkbox-image: "../images/backgrounds/form-checkbox.svg"; -@internal-form-checkbox-indeterminate-image: "../images/backgrounds/form-checkbox-indeterminate.svg"; \ No newline at end of file +@internal-form-select-image: "/uikit/images/backgrounds/form-select.svg"; +@internal-form-datalist-image: "/uikit/images/backgrounds/form-datalist.svg"; +@internal-form-radio-image: "/uikit/images/backgrounds/form-radio.svg"; +@internal-form-checkbox-image: "/uikit/images/backgrounds/form-checkbox.svg"; +@internal-form-checkbox-indeterminate-image: "/uikit/images/backgrounds/form-checkbox-indeterminate.svg"; \ No newline at end of file diff --git a/client/less/site/uikit-theme.dtp-light.less b/client/less/site/uikit-theme.dtp-light.less new file mode 100644 index 00000000..a737de82 --- /dev/null +++ b/client/less/site/uikit-theme.dtp-light.less @@ -0,0 +1,25 @@ +// +// Colors +// + +@site-brand-color: #ff0013; +@button-label-color: #2a2a2a; +@button-label-hover: #ffffff; +@social-link-color: #2a2a2a; +@checkout-button-text-color: #2a2a2a; + +// +// Component: Navbar +// + +@navbar-nav-item-height: @site-navbar-height; + +// +// Form +// + +@internal-form-select-image: "/uikit/images/backgrounds/form-select.svg"; +@internal-form-datalist-image: "/uikit/images/backgrounds/form-datalist.svg"; +@internal-form-radio-image: "/uikit/images/backgrounds/form-radio.svg"; +@internal-form-checkbox-image: "/uikit/images/backgrounds/form-checkbox.svg"; +@internal-form-checkbox-indeterminate-image: "/uikit/images/backgrounds/form-checkbox-indeterminate.svg"; \ No newline at end of file diff --git a/client/less/site/variables.less b/client/less/site/variables.less index 36104fc3..a4169bfc 100644 --- a/client/less/site/variables.less +++ b/client/less/site/variables.less @@ -1,2 +1 @@ -@site-brand-color: #d01a2a; @site-navbar-height: 64px; \ No newline at end of file diff --git a/client/less/style.less b/client/less/style.common.less similarity index 67% rename from client/less/style.less rename to client/less/style.common.less index c7c81a1c..9fb7aeaf 100644 --- a/client/less/style.less +++ b/client/less/style.common.less @@ -1,8 +1,3 @@ -@import "site/variables.less"; - -@import "node_modules/uikit/src/less/uikit.theme.less"; -@import "site/uikit-theme.less"; - @import "site/main.less"; @import "site/border.less"; @import "site/comment.less"; @@ -15,7 +10,6 @@ @import "site/dashboard.less"; @import "site/site.less"; -@import "site/site-home.less"; @import "site/form.less"; @import "site/button.less"; diff --git a/config/job-queues.js b/config/job-queues.js index 1f814ec5..7c5dc581 100644 --- a/config/job-queues.js +++ b/config/job-queues.js @@ -1,11 +1,11 @@ // job-queues.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; module.exports = { - // 'dvr-ingest': { + // 'queue-name': { // attempts: 3, // }, }; \ No newline at end of file diff --git a/config/limiter.js b/config/limiter.js index 8cab4e78..350e8557 100644 --- a/config/limiter.js +++ b/config/limiter.js @@ -1,12 +1,12 @@ // limiter.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const ONE_SECOND = 1000; const ONE_MINUTE = ONE_SECOND * 60; -// const ONE_HOUR = ONE_MINUTE * 60; +const ONE_HOUR = ONE_MINUTE * 60; module.exports = { @@ -71,34 +71,18 @@ module.exports = { }, /* - * CryptoExchangeController + * EmailController */ - cryptoExchange: { - getRateGraph: { + email: { + getEmailOptOut: { total: 10, - expire: ONE_MINUTE, - message: 'You are loading exchange rate graphs too quickly', + expire: ONE_HOUR, + message: "You really don't need to do that this much.", }, - getCurrentRates: { + getEmailVerify: { total: 10, - expire: ONE_MINUTE, - message: 'You are loading cryptocurrency exchange rates too quickly', - }, - }, - - /* - * DashboardController - */ - dashboard: { - getEpisodeView: { - total: 15, - expire: ONE_MINUTE, - message: 'You are loading the dashboard episode view too quickly', - }, - getHome: { - total: 15, - expire: ONE_MINUTE, - message: 'You are loading the publisher dashboard too quickly', + expire: ONE_HOUR, + message: "You really don't need to do that this much and can stop.", }, }, @@ -143,54 +127,6 @@ module.exports = { } }, - /* - * NewsletterController - */ - newsletter: { - getView: { - total: 5, - expire: ONE_MINUTE, - message: 'You are reading newsletters too quickly', - }, - getIndex: { - total: 60, - expire: ONE_MINUTE, - message: 'You are fetching newsletters too quickly', - }, - }, - - /* - * PageController - */ - page: { - getView: { - total: 5, - expire: ONE_MINUTE, - message: 'You are reading pages too quickly', - }, - }, - - /* - * PostController - */ - post: { - getComments: { - total: 20, - expire: ONE_MINUTE, - message: 'You are reading comments too quickly', - }, - getView: { - total: 5, - expire: ONE_MINUTE, - message: 'You are reading posts too quickly', - }, - getIndex: { - total: 60, - expire: ONE_MINUTE, - message: 'You are refreshing too quickly', - }, - }, - /* * UserController */ @@ -210,6 +146,16 @@ module.exports = { expire: ONE_MINUTE, message: 'You are updating account settings too quickly', }, + getOtpSetup: { + total: 10, + expire: ONE_MINUTE, + message: 'You are configuring two-factor authentication too quickly', + }, + getOtpDisable: { + total: 10, + expire: ONE_MINUTE, + message: 'You are disabling two-factor authentication too quickly', + }, getSettings: { total: 8, expire: ONE_MINUTE, @@ -232,4 +178,4 @@ module.exports = { expire: ONE_MINUTE, message: 'You are loading these pages too quickly', }, -}; +}; \ No newline at end of file diff --git a/config/pinned-packages.js b/config/pinned-packages.js new file mode 100644 index 00000000..ac808ad8 --- /dev/null +++ b/config/pinned-packages.js @@ -0,0 +1,34 @@ +// pinned-packages.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache 2.0 + +/* + * The update-deps.js script automates updating dependencies, and will honor + * version specifications found here, if any. + * + * This enables the developer to "pin" a package at a version specification that + * is guaranteed to work with the application. + */ + +'use strict'; + +module.exports = { + /* + * FontAwesome is being held back because they've lost their damn mind if they + * think I'm changing every CSS class. I'll stay on 5.15.4 forever. + */ + "@fortawesome/fontawesome-free": { + version: "5.15.4", + exact: true, + }, + + /* + * node-fetch is being held at latest 2.x because I can't convert everything + * to a module quite yet (required for 3+). And, it's not that critical of a + * tool in this application stack. + */ + "node-fetch": { + version: "2", + exact: false, + }, +}; \ No newline at end of file diff --git a/config/reserved-names.js b/config/reserved-names.js new file mode 100644 index 00000000..8bb5012c --- /dev/null +++ b/config/reserved-names.js @@ -0,0 +1,30 @@ +// reserved-names.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache 2.0 + +'use strict'; + +module.exports = [ + '.env', + '.env-default', + 'about', + 'admin', + 'auth', + 'digitaltelepresence', + 'dist', + 'dtp', + 'fontawesome', + 'fonts', + 'img', + 'image', + 'less', + 'manifest', + 'manifest.json', + 'moment', + 'newsletter', + 'numeral', + 'socket.io', + 'uikit', + 'user', + 'welcome', +]; \ No newline at end of file diff --git a/config/site.js b/config/site.js new file mode 100644 index 00000000..a44e42a0 --- /dev/null +++ b/config/site.js @@ -0,0 +1,13 @@ +// site.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache 2.0 + +'use strict'; + +module.exports = { + name: process.env.DTP_SITE_NAME, + description: process.env.DTP_SITE_DESCRIPTION, + domain: process.env.DTP_SITE_DOMAIN, + domainKey: process.env.DTP_SITE_DOMAIN_KEY, + company: process.env.DTP_SITE_COMPANY || 'Digital Telepresence, LLC', +}; \ No newline at end of file diff --git a/docs/services.md b/docs/services.md index d69d9b51..2cb68c69 100644 --- a/docs/services.md +++ b/docs/services.md @@ -1,4 +1,4 @@ -# DTP Sites: Services +# DTP Base: 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. diff --git a/dtp-sites-cli.js b/dtp-webapp-cli.js similarity index 66% rename from dtp-sites-cli.js rename to dtp-webapp-cli.js index b77f9af1..56d09bfc 100644 --- a/dtp-sites-cli.js +++ b/dtp-webapp-cli.js @@ -1,9 +1,11 @@ -// dtp-sites-cli.js -// Copyright (C) 2021 Digital Telepresence, LLC +// dtp-webapp-cli.js +// Copryright (C) DTP Technologies, LLC // License: Apache-2.0 'use strict'; +const DTP_COMPONENT_NAME = 'webapp-cli'; + require('dotenv').config(); const path = require('path'); @@ -16,17 +18,12 @@ const { SiteLog, } = require(path.join(__dirname, 'lib', 'site-lib')); -module.pkg = require(path.join(__dirname, 'package.json')); +module.rootPath = __dirname; +module.pkg = require(path.join(module.rootPath, 'package.json')); module.config = { - componentName: 'sites-cli', - root: __dirname, - site: { - name: process.env.DTP_SITE_NAME, - description: process.env.DTP_SITE_DESCRIPTION, - domain: process.env.DTP_SITE_DOMAIN, - domainKey: process.env.DTP_SITE_DOMAIN_KEY, - company: process.env.DTP_SITE_COMPANY || 'Digital Telepresence, LLC', - }, + componentName: DTP_COMPONENT_NAME, + root: module.rootPath, + site: require(path.join(module.rootPath, 'config', 'site')), }; module.log = new SiteLog(module, module.config.componentName); @@ -79,12 +76,6 @@ module.revokePermission = async (target, permission) => { } }; -module.dvrIngest = async (episodeId) => { - const jobQueue = module.services.jobQueue.getJobQueue('dvr-ingest', module.config.jobQueues['dvr-ingest']); - const job = await jobQueue.add({ episodeId }); - module.log.info('job created', { id: job.id }); -}; - module.deleteOtpAccount = async (target) => { const { otpAuth: otpAuthService } = module.services; const User = mongoose.model('User'); @@ -116,6 +107,67 @@ module.resetIndexes = async ( ) => { } }; +module.resetPassword = async (username) => { + const { user: userService } = module.services; + const User = mongoose.model('User'); + + const user = await User + .findOne({ + username_lc: username.trim().toLowerCase(), + }) + .lean(); + + if (!user) { + throw new Error('user not found'); + } + + const password = generatePassword(8, true); + module.log.info('updating user password', { username, password }); + await userService.updatePassword(user, password); +}; + +module.sendWelcomeEmail = async (username) => { + const User = mongoose.model('User'); + + if (username === 'all') { + await User + .find() + .select('+flags +optIn') + .cursor() + .eachAsync(module.sendUserWelcomeEmail, 4); + } else { + const user = await User + .findOne({ + username_lc: username.trim().toLowerCase(), + }) + .select('+flags +optIn') + .lean(); + if (!user) { + throw new Error('user not found'); + } + await module.sendUserWelcomeEmail(user); + } +}; + +module.sendUserWelcomeEmail = async (user) => { + const { user: userService } = module.services; + + if (user.flags.isEmailVerified) { + module.log.alert('user is email-verified (aboring welcome email)', { + user: user._id, + username: user.username, + }); + return; + } + + module.log.info('sending welcome email', { + user: user._id, + username: user.username, + email: user.email, + }); + await userService.sendWelcomeEmail(user); +}; + /* * SERVER INIT */ @@ -130,21 +182,21 @@ module.resetIndexes = async ( ) => { short: 'a', type: 'string', description: 'The action to perform', - example: 'sites-cli --action=grant --permission=moderator email email ...', + example: 'dtp-webapp-cli --action=grant --permission=moderator email email ...', }, { name: 'permission', short: 'p', type: 'string', description: 'The permission(s) being added or removed', - example: 'sites-cli --action=grant --permission=admin email email ...' + example: 'dtp-webapp-cli --action=grant --permission=admin email email ...' }, ]); try { await SitePlatform.startPlatform(module); } catch (error) { - module.log.error('failed to start DTP Sites platform', { error }); + module.log.error(`failed to start DTP ${DTP_COMPONENT_NAME} platform`, { error }); return; } @@ -162,10 +214,6 @@ module.resetIndexes = async ( ) => { await module.revokePermission(target, module.app.options.permission); break; - case 'dvr-ingest': - await module.dvrIngest(target); - break; - case 'make-bucket': await module.makeBucket(target); break; @@ -174,10 +222,18 @@ module.resetIndexes = async ( ) => { await module.deleteOtpAccount(target); break; + case 'reset-password': + await module.resetPassword(target); + break; + case 'reset-indexes': await module.resetIndexes(); break; + case 'welcome-email': + await module.sendWelcomeEmail(target); + break; + default: throw new Error(`invalid action: ${module.app.options.action}`); } diff --git a/dtp-sites.js b/dtp-webapp.js similarity index 70% rename from dtp-sites.js rename to dtp-webapp.js index 2deef8e6..4597acca 100644 --- a/dtp-sites.js +++ b/dtp-webapp.js @@ -1,26 +1,23 @@ -// dtp-sites.js -// Copryright (C) Digital Telepresence, LLC +// dtp-webapp.js +// Copryright (C) DTP Technologies, LLC // Licence: Apache-2.0 'use strict'; +const DTP_COMPONENT_NAME = 'webapp'; + require('dotenv').config(); const path = require('path'); const { SitePlatform, SiteLog } = require(path.join(__dirname, 'lib', 'site-lib')); -module.pkg = require(path.join(__dirname, 'package.json')); +module.rootPath = __dirname; +module.pkg = require(path.join(module.rootPath, 'package.json')); module.config = { - componentName: 'sites', - root: __dirname, - site: { - name: process.env.DTP_SITE_NAME, - description: process.env.DTP_SITE_DESCRIPTION, - domain: process.env.DTP_SITE_DOMAIN, - domainKey: process.env.DTP_SITE_DOMAIN_KEY, - company: process.env.DTP_SITE_COMPANY || 'Digital Telepresence, LLC', - }, + componentName: DTP_COMPONENT_NAME, + root: module.rootPath, + site: require(path.join(module.rootPath, 'config', 'site')), http: { address: process.env.HTTP_BIND_ADDRESS, port: parseInt(process.env.HTTP_BIND_PORT, 10), @@ -29,6 +26,10 @@ module.config = { module.log = new SiteLog(module, module.config.componentName); +module.shutdown = async ( ) => { + +}; + (async ( ) => { process.on('unhandledRejection', (error, p) => { @@ -46,6 +47,7 @@ module.log = new SiteLog(module, module.config.componentName); process.once('SIGINT', async ( ) => { module.log.info('SIGINT received'); module.log.info('requesting shutdown...'); + await module.shutdown(); const exitCode = await SitePlatform.shutdown(); process.nextTick(( ) => { process.exit(exitCode); @@ -61,7 +63,7 @@ module.log = new SiteLog(module, module.config.componentName); await SitePlatform.startPlatform(module); await SitePlatform.startWebServer(module); } catch (error) { - module.log.error('failed to start DTP Sites web server', { error }); + module.log.error(`failed to start DTP ${DTP_COMPONENT_NAME}`, { error }); process.exit(-1); } diff --git a/gulpfile.js b/gulpfile.js index c041b9d5..52f8d765 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -28,8 +28,8 @@ function util_start_browsersync ( ) { host: 'localhost', open: 'local', // https: { - // key: path.join(__dirname, 'ssl', 'dtp-sites.key'), - // cert: path.join(__dirname, 'ssl', 'dtp-sites.crt'), + // key: path.join(__dirname, 'ssl', 'dtp-webapp.key'), + // cert: path.join(__dirname, 'ssl', 'dtp-webapp.crt'), // }, port: 3333, cors: true, @@ -57,15 +57,22 @@ function util_start_browsersync ( ) { }); } -function dtp_less ( ) { +async function dtp_less_dark ( ) { return gulp - .src('./client/less/style.less') - .pipe(plumber()) - .pipe(less({ - math: 'always', - })) - .pipe(gulp.dest('./dist/css')) - .pipe(browserSync.stream()); + .src('./client/less/dtp-dark.less') + .pipe(plumber()) + .pipe(less({ paths: [ __dirname ], math: 'always' })) + .pipe(gulp.dest('./dist/css')) + .pipe(browserSync.stream()); +} + +async function dtp_less_light ( ) { + return gulp + .src('./client/less/dtp-light.less') + .pipe(plumber()) + .pipe(less({ paths: [ __dirname ], math: 'always' })) + .pipe(gulp.dest('./dist/css')) + .pipe(browserSync.stream()); } function dtp_jshint_client ( ) { @@ -141,7 +148,7 @@ const webpackResolve = { extensions: ['.js'], }; const webpackOutput = { - filename: 'dtpsites-[name].js', + filename: 'dtpweb-[name].js', path: path.resolve(__dirname, 'dist', 'js') }; @@ -220,9 +227,8 @@ function dtp_jsbuild_prod ( ) { function dtp_watch_client (done) { var files = [ './client/less/**/*.less', - './client/less/dtp/*.less' ]; - gulp.watch(files, dtp_less); + gulp.watch(files, exports.less); var jsfiles = [ path.join(__dirname, '..', 'dtp', 'dtp-lib', 'client', '**', '*.js'), @@ -252,7 +258,7 @@ function dtp_watch_client (done) { function dtp_develop (done) { var isFirst = true; nodemon({ - script: 'dtp-sites.js', + script: 'dtp-webapp.js', verbose: false, watch: [ './*.js', @@ -270,7 +276,7 @@ function dtp_develop (done) { }) .on('readable', function ( ) { this.stdout.on('data', (chunk) => { - if (/sites platform online/.test(chunk)) { + if (/platform online/.test(chunk)) { if (isFirst) { util_start_browsersync(); isFirst = false; @@ -284,7 +290,10 @@ function dtp_develop (done) { }); } -exports.less = dtp_less; +exports.less = gulp.series( + dtp_less_light, + dtp_less_dark, +); exports.jshint = gulp.parallel( dtp_jshint_models, @@ -296,13 +305,8 @@ exports.jshint = gulp.parallel( ); exports.build = gulp.parallel( - dtp_less, - dtp_jshint_models, - dtp_jshint_lib, - dtp_jshint_controllers, - dtp_jshint_client, - dtp_jshint_workers, - dtp_jshint_services, + exports.less, + exports.jshint, dtp_jsbuild_dev, dtp_jsbuild_prod, ); diff --git a/lib/client/js/dtp-app.js b/lib/client/js/dtp-app.js index 5835453f..ffa93970 100644 --- a/lib/client/js/dtp-app.js +++ b/lib/client/js/dtp-app.js @@ -1,5 +1,5 @@ // dtp-app.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/client/js/dtp-display-engine.js b/lib/client/js/dtp-display-engine.js index b0fc9dd5..009d4d5c 100644 --- a/lib/client/js/dtp-display-engine.js +++ b/lib/client/js/dtp-display-engine.js @@ -1,5 +1,5 @@ // dtp-display-engine.js -// Copyright (C) 2021 Digital Telepresence, Inc. +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -215,4 +215,8 @@ export default class DtpDisplayEngine { async navigateTo (displayList, command) { window.location = command.params.href; } + + async reload ( ) { + window.location.reload(); + } } \ No newline at end of file diff --git a/lib/client/js/dtp-log.js b/lib/client/js/dtp-log.js index 8434b33e..acea612b 100644 --- a/lib/client/js/dtp-log.js +++ b/lib/client/js/dtp-log.js @@ -1,5 +1,5 @@ // dtpweb-log.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/client/js/dtp-plugin.js b/lib/client/js/dtp-plugin.js index 474b65a7..c3954113 100644 --- a/lib/client/js/dtp-plugin.js +++ b/lib/client/js/dtp-plugin.js @@ -1,5 +1,5 @@ // dtp-plugin.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 /** diff --git a/lib/client/js/dtp-socket.js b/lib/client/js/dtp-socket.js index 809252f2..b0e1984d 100644 --- a/lib/client/js/dtp-socket.js +++ b/lib/client/js/dtp-socket.js @@ -1,5 +1,5 @@ // dtpweb-socket.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-async.js b/lib/site-async.js index 582e636a..2a1af762 100644 --- a/lib/site-async.js +++ b/lib/site-async.js @@ -1,5 +1,5 @@ // site-async.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-common.js b/lib/site-common.js index 1438cd46..6886b8df 100644 --- a/lib/site-common.js +++ b/lib/site-common.js @@ -1,15 +1,19 @@ // site-common.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; +const path = require('path'); +const pug = require('pug'); + const Events = require('events'); class SiteCommon extends Events { constructor (dtp) { super(); this.dtp = dtp; + this.appTemplateRoot = path.join(this.dtp.config.root, 'app', 'templates'); } saveSession (req) { @@ -22,6 +26,19 @@ class SiteCommon extends Events { }); }); } + + isValidString (text) { + return text && (typeof text === 'string') && (text.length > 0); + } + + loadAppTemplate (type, name) { + return pug.compileFile(path.join(this.appTemplateRoot, type, name)); + } + + loadViewTemplate (filename) { + const scriptFile = path.join(this.dtp.config.root, 'app', 'views', filename); + return pug.compileFile(scriptFile); + } } module.exports.SiteCommon = SiteCommon; \ No newline at end of file diff --git a/lib/site-controller.js b/lib/site-controller.js index 84196d4d..1b0b725a 100644 --- a/lib/site-controller.js +++ b/lib/site-controller.js @@ -1,5 +1,5 @@ // site-controller.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-error.js b/lib/site-error.js index 66018399..521b3d41 100644 --- a/lib/site-error.js +++ b/lib/site-error.js @@ -1,5 +1,5 @@ // site-error.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-ioserver.js b/lib/site-ioserver.js index d62c72b0..6cfeb660 100644 --- a/lib/site-ioserver.js +++ b/lib/site-ioserver.js @@ -1,5 +1,5 @@ // site-ioserver.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-lib.js b/lib/site-lib.js index 54cda917..882b5314 100644 --- a/lib/site-lib.js +++ b/lib/site-lib.js @@ -1,5 +1,5 @@ // site-lib.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-log.js b/lib/site-log.js index 825721e1..f894db09 100644 --- a/lib/site-log.js +++ b/lib/site-log.js @@ -1,5 +1,5 @@ // site-log.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/lib/site-platform.js b/lib/site-platform.js index 1bed4239..783efdb6 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -1,5 +1,5 @@ // site-platform.js -// Copyright (C) 2021 Digital Telepresence, LLC. +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; @@ -200,8 +200,6 @@ module.exports.startPlatform = async (dtp) => { }; module.exports.startWebServer = async (dtp) => { - const { page: pageService } = module.services; - dtp.app = module.app = express(); module.app.set('views', path.join(dtp.config.root, 'app', 'views')); @@ -339,7 +337,6 @@ module.exports.startWebServer = async (dtp) => { return next(error); } }); - module.app.use(pageService.menuMiddleware.bind(pageService)); /* * System Init diff --git a/lib/site-service.js b/lib/site-service.js index 16fb463f..542089cb 100644 --- a/lib/site-service.js +++ b/lib/site-service.js @@ -1,5 +1,5 @@ // site-service.js -// Copyright (C) 2021 Digital Telepresence, LLC +// Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; diff --git a/package.json b/package.json index 8b32e6a6..197f386f 100644 --- a/package.json +++ b/package.json @@ -3,75 +3,76 @@ "version": "0.2.22", "description": "Open source blogging engine for the Digital Telepresence Platform.", "main": "dtp-sites.js", - "author": "Digital Telepresence, LLC", + "author": "DTP Technologies, LLC", "license": "Apache-2.0", "private": true, "scripts": { - "start": "node dtp-sites.js", - "dev": "nodemon dtp-sites.js" + "start": "node dtp-webapp.js", + "dev": "nodemon dtp-webapp.js" }, "dependencies": { "@fortawesome/fontawesome-free": "^5.15.4", "@joeattardi/emoji-button": "^4.6.2", "@socket.io/redis-adapter": "^7.1.0", "anchorme": "^2.1.2", - "ansicolor": "^1.1.95", + "ansicolor": "^1.1.100", "argv": "^0.0.2", - "bull": "^4.1.1", - "chart.js": "^3.6.2", + "bull": "^4.7.0", + "chart.js": "^3.7.1", "chartjs-adapter-moment": "^1.0.0", "compression": "^1.7.4", - "connect-redis": "^6.0.0", + "connect-redis": "^6.1.1", "cookie-parser": "^1.4.6", "cron": "^1.8.2", "cropperjs": "^1.5.12", "diskusage-ng": "^1.0.2", "disposable-email-provider-domains": "^1.0.9", - "dotenv": "^10.0.0", + "dotenv": "^16.0.0", "dtp-jshint-reporter": "^1.0.2", "ein-validator": "^1.0.1", "email-domain-check": "^1.1.4", "email-validator": "^2.0.4", - "execa": "^6.0.0", - "express": "^4.17.1", + "execa": "^6.1.0", + "express": "^4.17.3", "express-limiter": "^1.6.1", "express-session": "^1.17.2", "feed": "^4.2.2", - "geoip-lite": "^1.4.2", + "geoip-lite": "^1.4.3", "glob": "^7.2.0", - "highlight.js": "^11.3.1", + "highlight.js": "^11.4.0", "html-filter": "^4.3.2", - "ioredis": "^4.28.2", + "ioredis": "^4.28.5", "jsdom": "^19.0.0", - "libphonenumber-js": "^1.9.44", - "marked": "^4.0.6", + "libphonenumber-js": "^1.9.49", + "marked": "^4.0.12", "method-override": "^3.0.0", - "minio": "^7.0.23", + "minio": "^7.0.26", "moment": "^2.29.1", - "mongoose": "^6.0.14", + "mongoose": "^6.2.4", "morgan": "^1.10.0", - "multer": "^1.4.3", + "multer": "^1.4.4", "node-fetch": "2", "nodemailer": "^6.7.2", "numeral": "^2.0.6", + "oauth2orize": "^1.11.1", "otplib": "^12.0.1", - "passport": "^0.5.0", + "passport": "^0.5.2", "passport-local": "^1.0.0", "passport-oauth2": "^1.6.1", "pug": "^3.0.2", "qrcode": "^1.5.0", "rate-limiter-flexible": "^2.3.6", - "rotating-file-stream": "^3.0.2", + "rotating-file-stream": "^3.0.3", "serve-favicon": "^2.5.0", - "sharp": "^0.29.3", - "slug": "^5.1.0", - "socket.io": "^4.4.0", + "sharp": "^0.30.2", + "slug": "^5.2.0", + "socket.io": "^4.4.1", "socket.io-emitter": "^3.2.0", "striptags": "^3.2.0", "svg-captcha": "^1.4.0", - "systeminformation": "^5.9.16", - "tinymce": "^5.10.2", - "uikit": "^3.9.4", + "systeminformation": "^5.11.6", + "tinymce": "^6.0.0", + "uikit": "^3.11.1", "uniqid": "^5.4.0", "url-validation": "^2.1.0", "uuid": "^8.3.2", @@ -87,10 +88,10 @@ "gulp-plumber": "^1.2.1", "gulp-rename": "^2.0.0", "gulp-uglify-es": "^3.0.0", - "jshint": "^2.13.1", - "terser-webpack-plugin": "^5.2.5", - "webpack": "^5.64.4", + "jshint": "^2.13.4", + "terser-webpack-plugin": "^5.3.1", + "webpack": "^5.69.1", "webpack-stream": "^7.0.0", - "workbox-webpack-plugin": "^6.4.2" + "workbox-webpack-plugin": "^6.5.0" } } diff --git a/release b/release index 0920766f..759e74df 100755 --- a/release +++ b/release @@ -20,4 +20,4 @@ git checkout master git pull . develop git push "$2" master -git checkout develop +git checkout develop \ No newline at end of file diff --git a/ssl/mkcert b/ssl/mkcert index dc605645..091f3f4f 100755 --- a/ssl/mkcert +++ b/ssl/mkcert @@ -1,7 +1,7 @@ #!/bin/bash -openssl genrsa -out dtp-sites.key -openssl req -new -out dtp-sites.csr -key dtp-sites.key -config openssl.cnf -openssl x509 -req -days 3650 -in dtp-sites.csr -signkey dtp-sites.key -out dtp-sites.crt -extensions v3_req -extfile openssl.cnf +openssl genrsa -out dtp-webapp.key +openssl req -new -out dtp-webapp.csr -key dtp-webapp.key -config openssl.cnf +openssl x509 -req -days 3650 -in dtp-webapp.csr -signkey dtp-webapp.key -out dtp-webapp.crt -extensions v3_req -extfile openssl.cnf -rm dtp-sites.csr \ No newline at end of file +rm dtp-webapp.csr \ No newline at end of file diff --git a/ssl/openssl.cnf b/ssl/openssl.cnf index 756f0d2b..d09b85a4 100644 --- a/ssl/openssl.cnf +++ b/ssl/openssl.cnf @@ -9,8 +9,8 @@ stateOrProvinceName = Pennsylvania stateOrProvinceName_default = Pennsylvania localityName = Pittsburgh localityName_default = Pittsburgh -organizationalUnitName = Digital Telepresence, LLC -organizationalUnitName_default = Digital Telepresence, LLC +organizationalUnitName = DTP Technologies, LLC +organizationalUnitName_default = DTP Technologies, LLC commonName = *.digitaltelepresence.com commonName_max = 64 diff --git a/start-local b/start-local index 2af3553f..329ee850 100755 --- a/start-local +++ b/start-local @@ -1,11 +1,16 @@ #!/bin/bash -MINIO_ROOT_USER="sites" -MINIO_ROOT_PASSWORD="1f1c7c9b-b833-4462-ae41-d56f52faa49c" -export MINIO_ROOT_USER MINIO_ROOT_PASSWORD +MINIO_CI_CD=1 + +MINIO_ROOT_USER="webapp" +MINIO_ROOT_PASSWORD="302888b9-c3d8-40f5-92de-6a3c57186af5" + +export MINIO_ROOT_USER MINIO_ROOT_PASSWORD MINIO_CI_CD forever start --killSignal=SIGINT app/workers/host-services.js +forever start --killSignal=SIGINT app/workers/reeeper.js minio server ./data/minio --console-address ":9001" -forever stop app/workers/host-services.js \ No newline at end of file +forever stop app/workers/reeeper.js +forever stop app/workers/host-services.js diff --git a/supervisord/dtp-sites.conf b/supervisord/dtp-sites.conf deleted file mode 100644 index 2df7a0bf..00000000 --- a/supervisord/dtp-sites.conf +++ /dev/null @@ -1,13 +0,0 @@ -[program:dtp-sites] -numprocs=1 -process_name=%(program_name)s_%(process_num)02d -command=/home/dtp/.nvm/versions/node/v16.13.0/bin/node --optimize_for_size --max_old_space_size=1024 --gc_interval=100 dtp-sites.js -directory=/home/dtp/live/dtp-sites -autostart=true -autorestart=true -startretries=3 -stopsignal=INT -stderr_logfile=/var/log/dtp-sites/dtp-sites.err.log -stdout_logfile=/var/log/dtp-sites/dtp-sites.out.log -user=dtp -environment=HOME='/home/dtp/live/dtp-sites',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=dtp-sites \ No newline at end of file diff --git a/supervisord/dtp-webapp.conf b/supervisord/dtp-webapp.conf new file mode 100644 index 00000000..08e19716 --- /dev/null +++ b/supervisord/dtp-webapp.conf @@ -0,0 +1,13 @@ +[program:dtp-webapp] +numprocs=1 +process_name=%(program_name)s_%(process_num)02d +command=/home/dtp/.nvm/versions/node/v16.13.0/bin/node --optimize_for_size --max_old_space_size=1024 --gc_interval=100 dtp-webapp.js +directory=/home/dtp/live/dtp-webapp +autostart=true +autorestart=true +startretries=3 +stopsignal=INT +stderr_logfile=/var/log/dtp-webapp/dtp-webapp.err.log +stdout_logfile=/var/log/dtp-webapp/dtp-webapp.out.log +user=dtp +environment=HOME='/home/dtp/live/dtp-webapp',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=dtp-webapp \ No newline at end of file diff --git a/supervisord/host-services.conf b/supervisord/host-services.conf index ef11c578..1be12d66 100644 --- a/supervisord/host-services.conf +++ b/supervisord/host-services.conf @@ -2,12 +2,12 @@ numprocs=1 process_name=%(program_name)s_%(process_num)02d command=/home/dtp/.nvm/versions/node/v16.13.0/bin/node --optimize_for_size --max_old_space_size=1024 --gc_interval=100 app/workers/host-services.js -directory=/home/dtp/live/dtp-sites +directory=/home/dtp/live/dtp-webapp autostart=true autorestart=true startretries=3 stopsignal=INT -stderr_logfile=/var/log/dtp-sites/host-services.err.log -stdout_logfile=/var/log/dtp-sites/host-services.out.log +stderr_logfile=/var/log/dtp-webapp/host-services.err.log +stdout_logfile=/var/log/dtp-webapp/host-services.out.log user=dtp -environment=HOME='/home/dtp/live/dtp-sites',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=host-services \ No newline at end of file +environment=HOME='/home/dtp/live/dtp-webapp',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=host-services \ No newline at end of file diff --git a/update-deps.js b/update-deps.js new file mode 100644 index 00000000..b7e4e4f8 --- /dev/null +++ b/update-deps.js @@ -0,0 +1,78 @@ +// update-deps.js +// Copyright (C) 2022 DTP Technologies, LLC +// License: Apache 2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'webapp'; + +require('dotenv').config(); + +const path = require('path'); + +const { spawn } = require('child_process'); + +const { SiteAsync, SiteLog } = require(path.join(__dirname, 'lib', 'site-lib')); + +module.rootPath = __dirname; +module.pkg = require(path.join(module.rootPath, 'package.json')); +module.pinnedPackages = require(path.join(module.rootPath, 'config', 'pinned-packages')); +module.log = new SiteLog(module, DTP_COMPONENT_NAME); + +module.runCommand = async (command, args) => { + return new Promise((resolve, reject) => { + module.log.info(command, { args }); + const cmd = spawn(command, args); + cmd.stdout.on('data', (data) => { + const output = data.toString('utf8'); + module.log.info(output); + }); + cmd.stderr.on('data', (data) => { + const output = data.toString('utf8'); + module.log.error(output); + }); + cmd.on('error', (err) => { + reject(err); + }); + cmd.on('close', (code) => { + resolve(code); + }); + }); +}; + +module.generateDepKeys = (dependencies) => { + const depKeys = Object.keys(dependencies).map((packageName) => { + const pin = module.pinnedPackages[packageName]; + if (!pin) { + return packageName; + } + if (pin.exact) { + return `${packageName}@${pin.version} --exact`; + } + return `${packageName}@${pin.version}`; + }); + return depKeys; +}; + +(async ( ) => { + + try { + module.log.info('updating dependencies'); + const depKeys = module.generateDepKeys(module.pkg.dependencies); + await module.runCommand('yarn', ['add', ...depKeys]); + } catch (error) { + module.error('failed to update dependencies', { error }); + process.exit(-1); + } + + try { + module.log.info('updating dev dependencies'); + const depKeys = module.generateDepKeys(module.pkg.devDependencies); + await module.runCommand('yarn', ['add', '--dev', ...depKeys]); + } catch (error) { + module.error('failed to update dev dependencies', { error }); + process.exit(-1); + } + + module.log.info('dependencies updated successfully. Good luck with that!'); +})(); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 181579dd..39f9d1c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1065,10 +1065,10 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/eslint-scope@^3.7.0": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" - integrity sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g== +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== dependencies: "@types/eslint" "*" "@types/estree" "*" @@ -1081,7 +1081,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.50": +"@types/estree@*": version "0.0.50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== @@ -1091,6 +1091,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "@types/json-schema@*", "@types/json-schema@^7.0.8": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -1277,7 +1282,7 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@~1.3.4, accepts@~1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -1285,6 +1290,14 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -1424,10 +1437,10 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= -ansicolor@^1.1.95: - version "1.1.95" - resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-1.1.95.tgz#978c494f04793d6c58115ba13a50f56593f736c6" - integrity sha512-R4yTmrfQZ2H9Wr5TZoM2iOz0+T6TNHqztpld7ZToaN8EaUj/06NG4r5UHQfegA9/+K/OY3E+WumprcglbcTMRA== +ansicolor@^1.1.100: + version "1.1.100" + resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-1.1.100.tgz#811f1afbf726edca3aafb942a14df8351996304a" + integrity sha512-Jl0pxRfa9WaQVUX57AB8/V2my6FJxrOR1Pp2qqFbig20QB4HzUoQ48THTKAgHlUCJeQm/s2WoOPcoIDhyCL/kw== anymatch@^2.0.0: version "2.0.0" @@ -1581,6 +1594,16 @@ asap@^2.0.6, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + assert-never@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" @@ -1628,7 +1651,7 @@ async@1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.1.1: +"async@2.1 - 2.6.3": version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -1818,21 +1841,31 @@ block-stream2@^2.0.0: dependencies: readable-stream "^3.4.0" -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== dependencies: - bytes "3.1.0" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" - http-errors "1.7.2" + http-errors "1.8.1" iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" + qs "6.9.7" + raw-body "2.4.3" + type-is "~1.6.18" boxen@^5.0.0: version "5.1.2" @@ -1879,6 +1912,16 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browser-or-node@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-1.3.0.tgz#f2a4e8568f60263050a6714b2cc236bb976647a7" + integrity sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg== + browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -1942,6 +1985,60 @@ browser-sync@^2.27.7: ua-parser-js "1.0.2" yargs "^15.4.1" +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + browserslist@^4.14.5, browserslist@^4.17.5, browserslist@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" @@ -1963,13 +2060,20 @@ bs-snippet-injector@^2.0.1: resolved "https://registry.yarnpkg.com/bs-snippet-injector/-/bs-snippet-injector-2.0.1.tgz#61b5393f11f52559ed120693100343b6edb04dd5" integrity sha1-YbU5PxH1JVntEgaTEANDtu2wTdU= -bson@^4.2.2, bson@^4.5.4: +bson@^4.2.2: version "4.6.0" resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.0.tgz#15c3b39ba3940c3d915a0c44d51459f4b4fbf1b2" integrity sha512-8jw1NU1hglS+Da1jDOUYuNcBJ4cNHCFIqzlwoFNnsTOg2R/ox0aTYcTiBN4dzRa9q7Cvy6XErh3L8ReTEb9AQQ== dependencies: buffer "^5.6.0" +bson@^4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.1.tgz#2b5da517539bb0f7f3ffb54ac70a384ca899641c" + integrity sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw== + dependencies: + buffer "^5.6.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1985,6 +2089,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1998,16 +2107,17 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== -bull@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.1.1.tgz#57fa7f9caa9ed74cd3fddc2301559d35f616bcef" - integrity sha512-EDkS3+QZyzHjC2gPThAlEwklIJSt2520nT6drpGfs5UlmVZnkTuA1unawUNLWjfN2lnwOJmstZ0t3/Rg/cRe8Q== +bull@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.7.0.tgz#89442d4676117edd9f9a1359bb0edfb489595e70" + integrity sha512-rnJIsuXrDjDlz3HMHz6xobiRZAWe3o4MJBkzx7FdUjO+K2nSYrhR2KpcL+ZCNUMPKtONxL4DqmRjat5SBHFlAw== dependencies: - cron-parser "^2.13.0" + cron-parser "^4.2.1" debuglog "^1.0.0" get-port "^5.1.1" - ioredis "^4.27.0" + ioredis "^4.28.5" lodash "^4.17.21" + msgpackr "^1.5.2" p-timeout "^3.2.0" semver "^7.3.2" uuid "^8.3.0" @@ -2025,16 +2135,16 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - bytes@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2091,6 +2201,14 @@ caniuse-lite@^1.0.30001280: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001284.tgz#d3653929ded898cd0c1f09a56fd8ca6952df4fca" integrity sha512-t28SKa7g6kiIQi6NHeOcKrOrGMzCRrXvlasPwWC26TH2QNdglgzQIRUuJ0cR3NeQPH+5jpuveeeSFDLm2zbkEw== +"chalk@4.1 - 4.1.2", chalk@^4.1.0, chalk@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2111,14 +2229,6 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0, chalk@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - character-parser@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0" @@ -2126,10 +2236,10 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.6.2.tgz#47342c551f688ffdda2cd53b534cb7e461ecec33" - integrity sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg== +chart.js@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada" + integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA== chartjs-adapter-moment@^1.0.0: version "1.0.0" @@ -2185,6 +2295,14 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -2335,15 +2453,15 @@ color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/color/-/color-4.1.0.tgz#9502e6a2dcacb26adf4c60910a27628d010b3de3" - integrity sha512-o2rkkxyLGgYoeUy1OodXpbPAQNmlNBrirQ8ODO8QutzDiDMNdezSOZLNnusQ6pUpCQJUsaJIo9DZJKqa2HgH7A== +color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" + integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== dependencies: color-convert "^2.0.1" color-string "^1.9.0" -colors@^1.1.2, colors@^1.2.1: +colors@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -2444,10 +2562,10 @@ connect-history-api-fallback@^1: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== -connect-redis@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/connect-redis/-/connect-redis-6.0.0.tgz#7e443fc7028eca43302bee59f51a315f65cd56b5" - integrity sha512-6eGEAAPHYvcfbRNCMmPzBIjrqRWLw7at9lCUH4G6NQ8gwWDJelaUmFNOqPIhehbw941euVmIuqWsaWiKXfb+5g== +connect-redis@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/connect-redis/-/connect-redis-6.1.1.tgz#f909ff6d39a5854658d596ac006d1ce901f3ddcd" + integrity sha512-4on27NmUz1hVCVsrUFUOe06YkgCx88ZZ29Wi7D5mXg22alN/Xx+Y+iPrJ1Cc7coUKAe5SP3W2LF/zo6WXSJFFg== connect@3.6.6: version "3.6.6" @@ -2479,12 +2597,12 @@ constantinople@^4.0.1: "@babel/parser" "^7.6.0" "@babel/types" "^7.6.1" -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: - safe-buffer "5.1.2" + safe-buffer "5.2.1" content-type@~1.0.4: version "1.0.4" @@ -2511,16 +2629,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - cookie@0.4.1, cookie@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + copy-anything@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" @@ -2562,13 +2680,43 @@ cors@~2.8.5: object-assign "^4" vary "^1" -cron-parser@^2.13.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" - integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: - is-nan "^1.3.0" - moment-timezone "^0.5.31" + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cron-parser@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.2.1.tgz#b43205d05ccd5c93b097dae64f3bd811f5993af3" + integrity sha512-5sJBwDYyCp+0vU5b7POl8zLWfgV5fOHxlc45FWoWdHecGC7MQHCjx0CHivCMRnGFovghKhhyYM+Zm9DcY5qcHg== + dependencies: + luxon "^1.28.0" cron@^1.8.2: version "1.8.2" @@ -2591,6 +2739,23 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-browserify@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -2635,7 +2800,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.6.9, debug@^2.2.0, debug@^2.3.3, debug@~2.6.4: +debug@2.6.9, debug@2.x.x, 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== @@ -2802,6 +2967,14 @@ depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -2812,10 +2985,10 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== dev-ip@^1.0.1: version "1.0.1" @@ -2830,6 +3003,15 @@ dicer@0.2.5: readable-stream "1.1.x" streamsearch "0.1.2" +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + dijkstrajs@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" @@ -2910,10 +3092,10 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== double-ended-queue@^2.1.0-0: version "2.1.0-0" @@ -2986,6 +3168,19 @@ electron-to-chromium@^1.3.896: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.11.tgz#303c9deebbe90c68bf5c2c81a88a3bf4522c8810" integrity sha512-2OhsaYgsWGhWjx2et8kaUcdktPbBGjKM2X0BReUCKcSCPttEY+hz2zie820JLbttU8jwL92+JJysWwkut3wZgA== +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + email-domain-check@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/email-domain-check/-/email-domain-check-1.1.4.tgz#b4001caaf7f7fa15fb8604dbb0739d2179578e8a" @@ -3284,19 +3479,27 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-6.0.0.tgz#598b46f09ae44f5d8097a30cfb1681d0f0371503" - integrity sha512-m4wU9j4Z9nXXoqT8RSfl28JSwmMNLFF69OON8H/lL3NeU0tNpGz313bcOfYoBBHokB0dC2tMl3VUcKgHELhL2Q== +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" + integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== dependencies: cross-spawn "^7.0.3" get-stream "^6.0.1" human-signals "^3.0.1" is-stream "^3.0.0" merge-stream "^2.0.0" - npm-run-path "^5.0.1" + npm-run-path "^5.1.0" onetime "^6.0.0" - signal-exit "^3.0.5" + signal-exit "^3.0.7" strip-final-newline "^3.0.0" exit@0.1.2, exit@0.1.x: @@ -3348,17 +3551,17 @@ express-session@^1.17.2: safe-buffer "5.2.1" uid-safe "~2.1.5" -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== +express@^4.17.3: + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" + body-parser "1.19.2" + content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.0" + cookie "0.4.2" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" @@ -3372,13 +3575,13 @@ express@^4.17.1: on-finished "~2.3.0" parseurl "~1.3.3" path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" + proxy-addr "~2.0.7" + qs "6.9.7" range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" + safe-buffer "5.2.1" + send "0.17.2" + serve-static "1.14.2" + setprototypeof "1.2.0" statuses "~1.5.0" type-is "~1.6.18" utils-merge "1.0.1" @@ -3742,18 +3945,18 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -geoip-lite@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/geoip-lite/-/geoip-lite-1.4.2.tgz#f41dc50086cce3bc31a6d2d578cad1c37f9f17b3" - integrity sha512-1rUNqar68+ldSSlSMdpLZPAM+NRokIDzB2lpQFRHSOaDVqtmy25jTAWe0lM2GqWFeaA35RiLhF8GF0vvL+qOKA== +geoip-lite@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/geoip-lite/-/geoip-lite-1.4.3.tgz#a0bb4e079f91691ab4d8eb41877f4e045123c922" + integrity sha512-eIK7DVUij4haNuzMu0cJNbFv0rrRxJGvom9BAu7mroFCGfh6jYZn51FaojlXN23k7Pyw04LPpd+HF6z5qhO6qQ== dependencies: - async "^2.1.1" - colors "^1.1.2" - iconv-lite "^0.4.13" - ip-address "^5.8.9" - lazy "^1.0.11" - rimraf "^2.5.2" - yauzl "^2.9.2" + async "2.1 - 2.6.3" + chalk "4.1 - 4.1.2" + iconv-lite "0.4.13 - 0.6.3" + ip-address "5.8.9 - 5.9.4" + lazy "1.0.11" + rimraf "2.5.2 - 2.7.1" + yauzl "2.9.2 - 2.10.0" get-caller-file@^1.0.1: version "1.0.3" @@ -3943,6 +4146,11 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + gulp-cli@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.3.0.tgz#ec0d380e29e52aa45e47977f0d32e18fd161122f" @@ -4145,10 +4353,36 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -highlight.js@^11.3.1: - version "11.3.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.3.1.tgz#813078ef3aa519c61700f84fe9047231c5dc3291" - integrity sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw== +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +highlight.js@^11.4.0: + version "11.4.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.4.0.tgz#34ceadd49e1596ee5aba3d99346cdfd4845ee05a" + integrity sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA== + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" homedir-polyfill@^1.0.1: version "1.0.3" @@ -4190,17 +4424,6 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-errors@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" @@ -4222,17 +4445,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.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" @@ -4264,20 +4476,20 @@ human-signals@^3.0.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== -iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@0.6.3: +"iconv-lite@0.4.13 - 0.6.3", iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + idb@^6.1.4: version "6.1.5" resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b" @@ -4365,10 +4577,10 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -ioredis@^4.27.0, ioredis@^4.28.2: - version "4.28.2" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.2.tgz#493ccd5d869fd0ec86c96498192718171f6c9203" - integrity sha512-kQ+Iv7+c6HsDdPP2XUHaMv8DhnSeAeKEwMbaoqsXYbO+03dItXt7+5jGQDRyjdRUV2rFJbzg7P4Qt1iX2tqkOg== +ioredis@^4.28.5: + version "4.28.5" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.5.tgz#5c149e6a8d76a7f8fa8a504ffc85b7d5b6797f9f" + integrity sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A== dependencies: cluster-key-slot "^1.1.0" debug "^4.3.1" @@ -4382,7 +4594,7 @@ ioredis@^4.27.0, ioredis@^4.28.2: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -ip-address@^5.8.9: +"ip-address@5.8.9 - 5.9.4": version "5.9.4" resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.9.4.tgz#4660ac261ad61bd397a860a007f7e98e4eaee386" integrity sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw== @@ -4391,6 +4603,11 @@ ip-address@^5.8.9: lodash "^4.17.15" sprintf-js "1.1.2" +ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -4604,14 +4821,6 @@ is-module@^1.0.0: resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-nan@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" - integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -4864,6 +5073,15 @@ jest-worker@^27.0.6: merge-stream "^2.0.0" supports-color "^8.0.0" +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + js-stringify@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" @@ -4922,10 +5140,10 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -jshint@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.13.1.tgz#16bbbecdbb4564d3758d9de4f24926f8c7f8f835" - integrity sha512-vymzfR3OysF5P774x6zYv0bD4EpH6NWRxpq54wO9mA9RuY49yb1teKSICkLx2Ryx+mfzlVVNNbTBtsRtg78t7g== +jshint@^2.13.4: + version "2.13.4" + resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.13.4.tgz#cee025a57c3f393d5455532d9ec7ccb018f890db" + integrity sha512-HO3bosL84b2qWqI0q+kpT/OpRJwo0R4ivgmxaO848+bo10rc50SkPnrtwSFXttW0ym4np8jbJvLwk5NziB7jIw== dependencies: cli "~1.0.0" console-browserify "1.1.x" @@ -4933,7 +5151,6 @@ jshint@^2.13.1: htmlparser2 "3.8.x" lodash "~4.17.21" minimatch "~3.0.2" - shelljs "0.3.x" strip-json-comments "1.0.x" json-buffer@3.0.0: @@ -5028,10 +5245,10 @@ just-debounce@^1.0.0: resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.1.0.tgz#2f81a3ad4121a76bc7cb45dbf704c0d76a8e5ddf" integrity sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ== -kareem@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93" - integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== +kareem@2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.4.tgz#b38c436fb4758775d919b2828b4009db59b52694" + integrity sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g== keyv@^3.0.0: version "3.1.0" @@ -5084,7 +5301,7 @@ latest-version@^5.1.0: dependencies: package-json "^6.3.0" -lazy@^1.0.11: +lazy@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690" integrity sha1-2qBoIGKCVCwIgojpdcKXwa53tpA= @@ -5140,10 +5357,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libphonenumber-js@^1.9.44: - version "1.9.44" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.44.tgz#d036364fe4c1e27205d1d283c7bf8fc25625200b" - integrity sha512-zhw8nUMJuQf7jG1dZfEOKKOS6M3QYIv3HnvB/vGohNd0QfxIQcObH3a6Y6s350H+9xgBeOXClOJkS0hJ0yvS3g== +libphonenumber-js@^1.9.49: + version "1.9.49" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.49.tgz#d431703cd699be2ccced5b95f26182a7c50a9227" + integrity sha512-/wEOIONcVboFky+lWlCaF7glm1FhBz11M5PHeCApA+xDdVfmhKjHktHS8KjyGxouV5CSXIr4f3GvLSpJa4qMSg== liftoff@^3.1.0: version "3.1.0" @@ -5279,6 +5496,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +luxon@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -5320,10 +5542,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.6.tgz#cd199503102b6807354f00574348d41ad4fd25d2" - integrity sha512-+H0bTf8DM8zLuFBUm/2VklxaCrwlBFgoJzHJcMZCnZ9cPgsllHwKpL6TPLdDeA38yPluMuVKOL1hO5w6HmG5Mg== +marked@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" + integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== matchdep@^2.0.0: version "2.0.0" @@ -5335,6 +5557,15 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5405,12 +5636,20 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.2.3" +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== -mime-types@^2.1.12, mime-types@^2.1.14, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.14, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.34" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== @@ -5442,6 +5681,16 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -5454,13 +5703,15 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minio@^7.0.23: - version "7.0.23" - resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.23.tgz#282496e929631ddea7d1d43976420b7095257d60" - integrity sha512-bBunl3dm3BmeDpbPuNp0WfsCRWxLQ+aFjyyXTodSeupKH2ewbCMmUkInF0UncS7jmyHRvEbHMQEOGA70CxtByg== +minio@^7.0.26: + version "7.0.26" + resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.26.tgz#83bcda813ed486a8bc7f028efa135d49b02d9bc8" + integrity sha512-knutnEZZMIUB/Xln6psVDrqObFKXDcF9m4IfFIX+zgDHYg3AlcF88DY1wdgg7bUkf+uU8iHkzP2q5CXAhia73w== dependencies: async "^3.1.0" block-stream2 "^2.0.0" + browser-or-node "^1.3.0" + crypto-browserify "^3.12.0" es6-error "^4.1.1" fast-xml-parser "^3.17.5" ipaddr.js "^2.0.1" @@ -5499,7 +5750,7 @@ mkdirp@^0.5.1, mkdirp@^0.5.4: dependencies: minimist "^1.2.5" -moment-timezone@^0.5.31, moment-timezone@^0.5.x: +moment-timezone@^0.5.x: version "0.5.34" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== @@ -5511,39 +5762,38 @@ moment-timezone@^0.5.31, moment-timezone@^0.5.x: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== -mongodb-connection-string-url@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.2.0.tgz#e2422bae91a953dc4ae5882e401301f5be39a227" - integrity sha512-U0cDxLUrQrl7DZA828CA+o69EuWPWEJTwdMPozyd7cy/dbtncUZczMw7wRHcwMD7oKOn0NM2tF9jdf5FFVW9CA== +mongodb-connection-string-url@^2.4.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz#f075c8d529e8d3916386018b8a396aed4f16e5ed" + integrity sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA== dependencies: "@types/whatwg-url" "^8.2.1" whatwg-url "^11.0.0" -mongodb@4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.1.4.tgz#ba8062c7c67e7a22db5a059dbac1e3044b48453b" - integrity sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ== +mongodb@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.3.1.tgz#e346f76e421ec6f47ddea5c8f5140e6181aaeb94" + integrity sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg== dependencies: - bson "^4.5.4" + bson "^4.6.1" denque "^2.0.1" - mongodb-connection-string-url "^2.1.0" + mongodb-connection-string-url "^2.4.1" + socks "^2.6.1" optionalDependencies: saslprep "^1.0.3" -mongoose@^6.0.14: - version "6.0.14" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.0.14.tgz#8f4d45318bb5de08814a8a9b040dd53521246608" - integrity sha512-SZ0kBlHrz/G70yWdVXLfM/gH4NsY85+as4MZRdtWxBTDEcmoE3rCFAz1/Ho2ycg5mJAeOBwdGZw4a5sn/WrwUA== +mongoose@^6.2.4: + version "6.2.4" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.2.4.tgz#c6ff4f84ab47b6c760e773424b0fd1f392d98563" + integrity sha512-3hA3IGxBzZdlp1+/I9qn53NjEAd01qvKAH2WUCPahjVO8+uAmR0B4m+1bC3x9a4r0ExY8QYQ2ryG3E/v5Tj+jA== dependencies: bson "^4.2.2" - kareem "2.3.2" - mongodb "4.1.4" + kareem "2.3.4" + mongodb "4.3.1" mpath "0.8.4" - mquery "4.0.0" - ms "2.1.2" - regexp-clone "1.0.0" - sift "13.5.2" - sliced "1.0.1" + mquery "4.0.2" + ms "2.1.3" + sift "16.0.0" morgan@^1.10.0: version "1.10.0" @@ -5561,14 +5811,12 @@ mpath@0.8.4: resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313" integrity sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g== -mquery@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.0.tgz#6c62160ad25289e99e0840907757cdfd62bde775" - integrity sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw== +mquery@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.2.tgz#a13add5ecd7c2e5a67e0f814b3c7acdfb6772804" + integrity sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA== dependencies: debug "4.x" - regexp-clone "^1.0.0" - sliced "1.0.1" ms@2.0.0: version "2.0.0" @@ -5585,15 +5833,30 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multer@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b" - integrity sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg== +msgpackr-extract@^1.0.14: + version "1.0.16" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-1.0.16.tgz#701c4f6e6f25c100ae84557092274e8fffeefe45" + integrity sha512-fxdRfQUxPrL/TizyfYfMn09dK58e+d65bRD/fcaVH4052vj30QOzzqxcQIS7B0NsqlypEQ/6Du3QmP2DhWFfCA== + dependencies: + nan "^2.14.2" + node-gyp-build "^4.2.3" + +msgpackr@^1.5.2: + version "1.5.4" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.4.tgz#2b6ea6cb7d79c0ad98fc76c68163c48eda50cf0d" + integrity sha512-Z7w5Jg+2Q9z9gJxeM68d7tSuWZZGnFIRhZnyqcZCa/1dKkhOCNvR1TUV3zzJ3+vj78vlwKRzUgVDlW4jiSOeDA== + optionalDependencies: + msgpackr-extract "^1.0.14" + +multer@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c" + integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw== dependencies: append-field "^1.0.0" busboy "^0.2.11" @@ -5609,7 +5872,7 @@ mute-stdout@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== -nan@^2.12.1: +nan@^2.12.1, nan@^2.14.2: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== @@ -5650,6 +5913,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -5667,10 +5935,10 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-addon-api@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" - integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-addon-api@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== node-fetch@2: version "2.6.6" @@ -5679,6 +5947,11 @@ node-fetch@2: dependencies: whatwg-url "^5.0.0" +node-gyp-build@^4.2.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" + integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + node-releases@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -5756,10 +6029,10 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npm-run-path@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.0.1.tgz#748dd68ed7de377bb1f7132c7dafe657be5ab400" - integrity sha512-ybBJQUSyFwEEhqO2lXmyKOl9ucHtyZBWVM0h0FiMfT/+WKxCUZFa95qAR2X3w/w6oigN3B0b2UNHZbD+kdfD5w== +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== dependencies: path-key "^4.0.0" @@ -5798,6 +6071,15 @@ oauth@0.9.x: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== +oauth2orize@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/oauth2orize/-/oauth2orize-1.11.1.tgz#00b6cafe2036a0a3aab0380627dc7cfd5b5e9a9c" + integrity sha512-9dSx/Gwm0J2Rvj4RH9+h7iXVnRXZ6biwWRgb2dCeQhCosODS0nYdM9I/G7BUGsjbgn0pHjGcn1zcCRtzj2SlRA== + dependencies: + debug "2.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6003,6 +6285,17 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -6077,10 +6370,10 @@ passport-strategy@1.x.x: resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= -passport@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/passport/-/passport-0.5.0.tgz#7914aaa55844f9dce8c3aa28f7d6b73647ee0169" - integrity sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg== +passport@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.5.2.tgz#0cb38dd8a71552c8390dfa6a9a6f7f3909954bcf" + integrity sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw== dependencies: passport-strategy "1.x.x" pause "0.0.1" @@ -6153,6 +6446,17 @@ pause@0.0.1: resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= +pbkdf2@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -6229,12 +6533,12 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prebuild-install@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" - integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== +prebuild-install@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" + integrity sha512-QBSab31WqkyxpnMWQxubYAHR5S9B2+r81ucocew34Fkl98FhvKIF50jIJnNOBmAZfyNV7vE5T6gd3hTVWgY6tg== dependencies: - detect-libc "^1.0.3" + detect-libc "^2.0.0" expand-template "^2.0.3" github-from-package "0.0.0" minimist "^1.2.3" @@ -6280,7 +6584,7 @@ promise@^7.0.1: dependencies: asap "~2.0.3" -proxy-addr@~2.0.5: +proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -6303,6 +6607,18 @@ pstree.remy@^1.1.8: resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + pug-attrs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41" @@ -6458,10 +6774,10 @@ qs@6.2.3: resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" integrity sha1-HPyyXBCpsrSDBT/zn138kjOQjP4= -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== querystring@0.2.0: version "0.2.0" @@ -6473,13 +6789,21 @@ random-bytes@~1.0.0: resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= -randombytes@^2.1.0: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + range-parser@~1.2.0, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -6490,13 +6814,13 @@ rate-limiter-flexible@^2.3.6: resolved "https://registry.yarnpkg.com/rate-limiter-flexible/-/rate-limiter-flexible-2.3.6.tgz#b1a2549dca91069c8a33d57c08a27262c0356c60" integrity sha512-8DVFOe89rreyut/vzwBI7vgXJynyYoYnH5XogtAKs0F/neAbCTTglXxSJ7fZeZamcFXZDvMidCBvps4KM+1srw== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== dependencies: - bytes "3.1.0" - http-errors "1.7.2" + bytes "3.1.2" + http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -6574,7 +6898,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.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== @@ -6682,11 +7006,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp-clone@1.0.0, regexp-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" - integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== - regexp.prototype.flags@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -6857,13 +7176,21 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@^2.5.2: +"rimraf@2.5.2 - 2.7.1": version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + rollup-plugin-terser@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" @@ -6881,10 +7208,10 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" -rotating-file-stream@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rotating-file-stream/-/rotating-file-stream-3.0.2.tgz#2e776cd3b8b734649d2da4e53b29c78389aa773b" - integrity sha512-P9PWUHjpkSk3QgDtOXKt/dE6NpjA00Yaa9kgZHf3Vq33hPjm4n42nc4TLeUqRhXsQ51X2Ywpsqtwbk7LlI9sCg== +rotating-file-stream@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/rotating-file-stream/-/rotating-file-stream-3.0.3.tgz#c2966a5eb6f2caf25cae18ec4fc50c5b1167a0f3" + integrity sha512-lN4tljs2Pv8OkVCw34Fa+jYn9vn98ZxWgBoavKjlT7az25F+oEZT6kKXmzpqovBVz+fW7Mb862tc34rpxKVZFw== rx@4.1.0: version "4.1.0" @@ -6908,7 +7235,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6920,7 +7247,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -7008,10 +7335,10 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +send@0.17.2: + version "0.17.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" + integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== dependencies: debug "2.6.9" depd "~1.1.2" @@ -7020,9 +7347,9 @@ send@0.17.1: escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.7.2" + http-errors "1.8.1" mime "1.6.0" - ms "2.1.1" + ms "2.1.3" on-finished "~2.3.0" range-parser "~1.2.1" statuses "~1.5.0" @@ -7075,15 +7402,15 @@ serve-static@1.13.2: parseurl "~1.3.2" send "0.16.2" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== +serve-static@1.14.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" + integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.1" + send "0.17.2" server-destroy@1.0.1: version "1.0.1" @@ -7110,27 +7437,30 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sharp@^0.29.3: - version "0.29.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" - integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: - color "^4.0.1" - detect-libc "^1.0.3" - node-addon-api "^4.2.0" - prebuild-install "^7.0.0" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +sharp@^0.30.2: + version "0.30.2" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.2.tgz#95b309b2740424702dc19b62a62595dd34a458b1" + integrity sha512-mrMeKI5ECTdYhslPlA2TbBtU3nZXMEBcQwI6qYXjPlu1LpW4HBZLFm6xshMI1HpIdEEJ3UcYp5AKifLT/fEHZQ== + dependencies: + color "^4.2.1" + detect-libc "^2.0.1" + node-addon-api "^4.3.0" + prebuild-install "^7.0.1" semver "^7.3.5" - simple-get "^4.0.0" + simple-get "^4.0.1" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -7146,11 +7476,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@0.3.x: - version "0.3.0" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" - integrity sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E= - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -7160,16 +7485,21 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -sift@13.5.2: - version "13.5.2" - resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6" - integrity sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA== +sift@16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.0.tgz#447991577db61f1a8fab727a8a98a6db57a23eb8" + integrity sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ== -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.5: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== +signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -7184,6 +7514,15 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -7191,15 +7530,15 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sliced@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" - integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= +slug@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/slug/-/slug-5.2.0.tgz#92f478dc01eedb6cd14def4c4cfe702409612204" + integrity sha512-jMCvtl7B2iF+2YUNeSo88iVy+wSGX2Jlfj6Hi7RvaQNTPiqJ8BYJAB6XaurEQiiVGN3gIj6u6IZvaG4Ang5brw== -slug@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/slug/-/slug-5.1.0.tgz#8a7e30ca1c3a6dc40cf74e269750913a865edb0b" - integrity sha512-IS39jKR6m+puU8zWgH6ruwx1sfzFNJ6Ai5PKIlUqd0X8C3ca7PB49Cvm0uayqgEt1jgaojO2wWEsQJngnh7fDA== +smart-buffer@^4.2.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" @@ -7317,10 +7656,10 @@ socket.io@2.4.0: socket.io-client "2.4.0" socket.io-parser "~3.4.0" -socket.io@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.4.0.tgz#8140a0db2c22235f88a6dceb867e4d5c9bd70507" - integrity sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ== +socket.io@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.4.1.tgz#cd6de29e277a161d176832bb24f64ee045c56ab8" + integrity sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg== dependencies: accepts "~1.3.4" base64id "~2.0.0" @@ -7329,6 +7668,14 @@ socket.io@^4.4.0: socket.io-adapter "~2.3.3" socket.io-parser "~4.0.4" +socks@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" + integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.2.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" @@ -7669,10 +8016,10 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -systeminformation@^5.9.16: - version "5.9.16" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.9.16.tgz#097d5b585401b209b3448d1fe84551a5a582b904" - integrity sha512-GDqen5wR9p3GVrTlyFYKbtQIE9eEhqd6Ya9Jr6HReSbDYJuYqhUgYTLuEt45qpSgNj1hKonUe/IzzdFXFmRBeg== +systeminformation@^5.11.6: + version "5.11.6" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.6.tgz#8624cbb2e95e6fa98a4ebb0d10759427c0e88144" + integrity sha512-7KBXgdnIDxABQ93w+GrPSrK/pup73+fM09VGka4A/+FhgzdlRY0JNGGDFmV8BHnFuzP9zwlI3n64yDbp7emasQ== tabbable@^4.0.0: version "4.0.0" @@ -7720,7 +8067,7 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5: +terser-webpack-plugin@^5.1.3: version "5.2.5" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.2.5.tgz#ce65b9880a0c36872555c4874f45bbdb02ee32c9" integrity sha512-3luOVHku5l0QBeYS8r4CdHYWEGMmIj3H1U64jgkdZzECcSOJAyJ9TjuqcQZvw1Y+4AOBN9SeYJPJmFn2cM4/2g== @@ -7731,6 +8078,17 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5: source-map "^0.6.1" terser "^5.7.2" +terser-webpack-plugin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== + dependencies: + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + terser "^5.7.2" + terser@^5.0.0, terser@^5.7.1, terser@^5.7.2: version "5.10.0" resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" @@ -7804,10 +8162,10 @@ tiny-inflate@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== -tinymce@^5.10.2: - version "5.10.2" - resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.2.tgz#cf1ff01025909be26c64348509e6de8e70d58e1d" - integrity sha512-5QhnZ6c8F28fYucLLc00MM37fZoAZ4g7QCYzwIl38i5TwJR5xGqzOv6YMideyLM4tytCzLCRwJoQen2LI66p5A== +tinymce@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.0.0.tgz#44945e4c12ea90d08aedf357304c24269a164e54" + integrity sha512-y4b5OhxScZiFovTgEFzI+2zEDSMfbJSQ4hfcYPg9HXvudxvDIHjc3fF73m0Sys5h8YtpWPG1fT9iJsjSnwuo2A== to-absolute-glob@^2.0.0: version "2.0.2" @@ -7871,11 +8229,6 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -7965,7 +8318,7 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -8017,10 +8370,10 @@ uid2@0.0.x: resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== -uikit@^3.9.4: - version "3.9.4" - resolved "https://registry.yarnpkg.com/uikit/-/uikit-3.9.4.tgz#2565a5c0239f49d3a75bfa89c519d40c006e3c46" - integrity sha512-mrOr/k3a/6/VBXgnqmQFg0l2ipZyoTpULZZgJ0RVHZz97HNrc28KQsb24pl8wxtw+BGpD39nfMhSmOznyOZuHw== +uikit@^3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/uikit/-/uikit-3.11.1.tgz#3f0b47f4b2e7610375c5f7cdbf550d086c81e22f" + integrity sha512-nUUtvE/wZe+oFF+8WLGlvsdHg1KI75SHHEhf5FcSVFG1AHEJkqOKhPhgU6FQLporAiPTsapzURN/bP765gI2bA== unbox-primitive@^1.0.1: version "1.0.1" @@ -8319,10 +8672,10 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -watchpack@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.0.tgz#a41bca3da6afaff31e92a433f4c856a0c25ea0c4" - integrity sha512-MnN0Q1OsvB/GGHETrFeZPQaOelWh/7O+EiFlj8sM9GPjtQkis7k01aAxrg/18kTfoIVcLL+haEVFlXDaSRwKRw== +watchpack@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" + integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -8359,10 +8712,10 @@ webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" - integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack-stream@^7.0.0: version "7.0.0" @@ -8378,13 +8731,13 @@ webpack-stream@^7.0.0: through "^2.3.8" vinyl "^2.2.1" -webpack@^5.64.4: - version "5.64.4" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.64.4.tgz#e1454b6a13009f57cc2c78e08416cd674622937b" - integrity sha512-LWhqfKjCLoYJLKJY8wk2C3h77i8VyHowG3qYNZiIqD6D0ZS40439S/KVuc/PY48jp2yQmy0mhMknq8cys4jFMw== +webpack@^5.69.1: + version "5.69.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.1.tgz#8cfd92c192c6a52c99ab00529b5a0d33aa848dc5" + integrity sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A== dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -8397,7 +8750,7 @@ webpack@^5.64.4: eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -8405,8 +8758,8 @@ webpack@^5.64.4: schema-utils "^3.1.0" tapable "^2.1.1" terser-webpack-plugin "^5.1.3" - watchpack "^2.3.0" - webpack-sources "^3.2.2" + watchpack "^2.3.1" + webpack-sources "^3.2.3" whatwg-encoding@^2.0.0: version "2.0.0" @@ -8529,25 +8882,25 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workbox-background-sync@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.4.2.tgz#bb31b95928d376abcb9bde0de3a0cef9bae46cf7" - integrity sha512-P7c8uG5X2k+DMICH9xeSA9eUlCOjHHYoB42Rq+RtUpuwBxUOflAXR1zdsMWj81LopE4gjKXlTw7BFd1BDAHo7g== +workbox-background-sync@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.0.tgz#50ba6bf19c71d21be29bb15ba0f317df7cfa8f44" + integrity sha512-rrekt/gt6qOIZsisj6QZfmAFPAnocq1Z603zAjt+qHmeXY8DLPOklVtvrXSaHoHH3qIjUq3SQY5s2x240iTIKw== dependencies: idb "^6.1.4" - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-broadcast-update@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.4.2.tgz#5094c4767dfb590532ac03ee07e9e82b2ac206bc" - integrity sha512-qnBwQyE0+PWFFc/n4ISXINE49m44gbEreJUYt2ldGH3+CNrLmJ1egJOOyUqqu9R4Eb7QrXcmB34ClXG7S37LbA== +workbox-broadcast-update@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.0.tgz#0104b9ea41b40f8c5e03780226de66bec15141f2" + integrity sha512-JC97c7tYqoGWcCfbKO9KHG6lkU+WhXCnDB2j1oFWEiv53nUHy3yjPpzMmAGNLD9oV5lInO15n6V18HfwgkhISw== dependencies: - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-build@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.4.2.tgz#47f9baa946c3491533cd5ccb1f194a7160e8a6e3" - integrity sha512-WMdYLhDIsuzViOTXDH+tJ1GijkFp5khSYolnxR/11zmfhNDtuo7jof72xPGFy+KRpsz6tug39RhivCj77qqO0w== +workbox-build@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.0.tgz#fd3579de7a91c188e8d857a4b265fe7170197204" + integrity sha512-da0/1b6//P9+ts7ofcIKcMVPyN6suJvjJASXokF7DsqvUmgRBPcCVV4KCy8QWjgfcz7mzuTpkSbdVHcPFJ/p0A== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -8567,138 +8920,136 @@ workbox-build@6.4.2: rollup "^2.43.1" rollup-plugin-terser "^7.0.0" source-map "^0.8.0-beta.0" - source-map-url "^0.4.0" stringify-object "^3.3.0" strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - workbox-background-sync "6.4.2" - workbox-broadcast-update "6.4.2" - workbox-cacheable-response "6.4.2" - workbox-core "6.4.2" - workbox-expiration "6.4.2" - workbox-google-analytics "6.4.2" - workbox-navigation-preload "6.4.2" - workbox-precaching "6.4.2" - workbox-range-requests "6.4.2" - workbox-recipes "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" - workbox-streams "6.4.2" - workbox-sw "6.4.2" - workbox-window "6.4.2" - -workbox-cacheable-response@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.4.2.tgz#ebcabb3667019da232e986a9927af97871e37ccb" - integrity sha512-9FE1W/cKffk1AJzImxgEN0ceWpyz1tqNjZVtA3/LAvYL3AC5SbIkhc7ZCO82WmO9IjTfu8Vut2X/C7ViMSF7TA== - dependencies: - workbox-core "6.4.2" - -workbox-core@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.4.2.tgz#f99fd36a211cc01dce90aa7d5f2c255e8fe9d6bc" - integrity sha512-1U6cdEYPcajRXiboSlpJx6U7TvhIKbxRRerfepAJu2hniKwJ3DHILjpU/zx3yvzSBCWcNJDoFalf7Vgd7ey/rw== - -workbox-expiration@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.4.2.tgz#61613459fd6ddd1362730767618d444c6b9c9139" - integrity sha512-0hbpBj0tDnW+DZOUmwZqntB/8xrXOgO34i7s00Si/VlFJvvpRKg1leXdHHU8ykoSBd6+F2KDcMP3swoCi5guLw== + workbox-background-sync "6.5.0" + workbox-broadcast-update "6.5.0" + workbox-cacheable-response "6.5.0" + workbox-core "6.5.0" + workbox-expiration "6.5.0" + workbox-google-analytics "6.5.0" + workbox-navigation-preload "6.5.0" + workbox-precaching "6.5.0" + workbox-range-requests "6.5.0" + workbox-recipes "6.5.0" + workbox-routing "6.5.0" + workbox-strategies "6.5.0" + workbox-streams "6.5.0" + workbox-sw "6.5.0" + workbox-window "6.5.0" + +workbox-cacheable-response@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.0.tgz#cf91b2d4f4707295539596a480ab1c908f6cbfdd" + integrity sha512-sqAtWAiBwWvI8HG/2Do7BeKPhHuUczt22ORkAjkH9DfTq9LuWRFd6T4HAMqX5G8F1gM9XA2UPlxRrEeSpFIz/A== + dependencies: + workbox-core "6.5.0" + +workbox-core@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.0.tgz#076e03840ca568bd04284e9f9f30e86c8dd09f1c" + integrity sha512-5SPwNipUzYBhrneLVT02JFA0fw3LG82jFAN/G2NzxkIW10t4MVZuML2nU94bbkgjq25u0fkY8+4JXzMfHgxEWQ== + +workbox-expiration@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.0.tgz#3cf6a0c8b08b59efa235d26d443c8b7f173179cd" + integrity sha512-y3WRkKRy/gMuZZNkrLFahjY0QZtLoq+QfhTbVAsOGHVg1CCtnNbeFAnEidQs7UisI2BK76VqQPvM7hEOFyZ92A== dependencies: idb "^6.1.4" - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-google-analytics@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.4.2.tgz#eea7d511b3078665a726dc2ee9f11c6b7a897530" - integrity sha512-u+gxs3jXovPb1oul4CTBOb+T9fS1oZG+ZE6AzS7l40vnyfJV79DaLBvlpEZfXGv3CjMdV1sT/ltdOrKzo7HcGw== +workbox-google-analytics@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.0.tgz#86ee42bd1a72ec5aa41f32631ab7c8e5cf4c1602" + integrity sha512-CHHh55wMNCc/BV1URrzEM2Zjgf6g2CV6QpAAc1pBRqaLY5755PeQZbp3o8KbJEM7YsC9mIBeQVsOkSKkGS30bg== dependencies: - workbox-background-sync "6.4.2" - workbox-core "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" + workbox-background-sync "6.5.0" + workbox-core "6.5.0" + workbox-routing "6.5.0" + workbox-strategies "6.5.0" -workbox-navigation-preload@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.4.2.tgz#35cd4ba416a530796af135410ca07db5bee11668" - integrity sha512-viyejlCtlKsbJCBHwhSBbWc57MwPXvUrc8P7d+87AxBGPU+JuWkT6nvBANgVgFz6FUhCvRC8aYt+B1helo166g== +workbox-navigation-preload@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.0.tgz#3b73753a40e4d0cbae9520de232f2fc515f2c0f5" + integrity sha512-ktrRQzXJ0zFy0puOtCa49wE3BSBGUB8KRMot3tEieikCkSO0wMLmiCb9GwTVvNMJLl0THRlsdFoI93si04nTxA== dependencies: - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-precaching@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.4.2.tgz#8d87c05d54f32ac140f549faebf3b4d42d63621e" - integrity sha512-CZ6uwFN/2wb4noHVlALL7UqPFbLfez/9S2GAzGAb0Sk876ul9ukRKPJJ6gtsxfE2HSTwqwuyNVa6xWyeyJ1XSA== +workbox-precaching@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.0.tgz#773d754b98f79cc13b646eaa7858e8b3ab740c37" + integrity sha512-IVLzgHx38T6LphJyEOltd7XAvpDi73p85uCT2ZtT1HHg9FAYC49a+5iHUVOnqye73fLW20eiAMFcnehGxz9RWg== dependencies: - workbox-core "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" + workbox-core "6.5.0" + workbox-routing "6.5.0" + workbox-strategies "6.5.0" -workbox-range-requests@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.4.2.tgz#050f0dfbb61cd1231e609ed91298b6c2442ae41b" - integrity sha512-SowF3z69hr3Po/w7+xarWfzxJX/3Fo0uSG72Zg4g5FWWnHpq2zPvgbWerBZIa81zpJVUdYpMa3akJJsv+LaO1Q== +workbox-range-requests@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.0.tgz#f36006f11aa86736ff815d200d0a5baf0e66c66e" + integrity sha512-+qTELdGZE5rOjuv+ifFrfRDN8Uvzpbm5Fal7qSUqB1V1DLCMxPwHCj6mWwQBRKBpW7G09kAwewH7zA3Asjkf/Q== dependencies: - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-recipes@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.4.2.tgz#68de41fa3a77b444b0f93c9c01a76ba1d41fd2bf" - integrity sha512-/oVxlZFpAjFVbY+3PoGEXe8qyvtmqMrTdWhbOfbwokNFtUZ/JCtanDKgwDv9x3AebqGAoJRvQNSru0F4nG+gWA== +workbox-recipes@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.0.tgz#8400fbb515ac14e15043f13197a37e971e4ed04f" + integrity sha512-7hWZAIcXmvr31NwYSWaQIrnThCH/Dx9+eYv/YdkpUeWIXRiHRkYvP1FdiHItbLSjL4Y6K7cy2Y9y5lGCkgaE4w== dependencies: - workbox-cacheable-response "6.4.2" - workbox-core "6.4.2" - workbox-expiration "6.4.2" - workbox-precaching "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" + workbox-cacheable-response "6.5.0" + workbox-core "6.5.0" + workbox-expiration "6.5.0" + workbox-precaching "6.5.0" + workbox-routing "6.5.0" + workbox-strategies "6.5.0" -workbox-routing@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.4.2.tgz#65b1c61e8ca79bb9152f93263c26b1f248d09dcc" - integrity sha512-0ss/n9PAcHjTy4Ad7l2puuod4WtsnRYu9BrmHcu6Dk4PgWeJo1t5VnGufPxNtcuyPGQ3OdnMdlmhMJ57sSrrSw== +workbox-routing@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.0.tgz#cbc085a74622d35d599f0b5352d2b46e9b2e7ba8" + integrity sha512-w1A9OVa/yYStu9ds0Dj+TC6zOAoskKlczf+wZI5mrM9nFCt/KOMQiFp1/41DMFPrrN/8KlZTS3Cel/Ttutw93Q== dependencies: - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-strategies@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.4.2.tgz#50c02bf2d116918e1a8052df5f2c1e4103c62d5d" - integrity sha512-YXh9E9dZGEO1EiPC3jPe2CbztO5WT8Ruj8wiYZM56XqEJp5YlGTtqRjghV+JovWOqkWdR+amJpV31KPWQUvn1Q== +workbox-strategies@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.0.tgz#40269f7bd8b3160b42f06fa027230370a8b6f981" + integrity sha512-Ngnwo+tfGw4uKSlTz3h1fYKb/lCV7SDI/dtTb8VaJzRl0N9XssloDGYERBmF6BN/DV/x3bnRsshfobnKI/3z0g== dependencies: - workbox-core "6.4.2" + workbox-core "6.5.0" -workbox-streams@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.4.2.tgz#3bc615cccebfd62dedf28315afb7d9ee177912a5" - integrity sha512-ROEGlZHGVEgpa5bOZefiJEVsi5PsFjJG9Xd+wnDbApsCO9xq9rYFopF+IRq9tChyYzhBnyk2hJxbQVWphz3sog== +workbox-streams@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.0.tgz#8c2fd0af9b8e1a25f865ff254c44f6554a248ce1" + integrity sha512-ZbeaZINkju4x45P9DFyRbOYInE+dyNAJIelflz4f9AOAdm+zZUJCooU4MdfsedVhHiTIA6pCD/3jCmW1XbvlbA== dependencies: - workbox-core "6.4.2" - workbox-routing "6.4.2" + workbox-core "6.5.0" + workbox-routing "6.5.0" -workbox-sw@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.4.2.tgz#9a6db5f74580915dc2f0dbd47d2ffe057c94a795" - integrity sha512-A2qdu9TLktfIM5NE/8+yYwfWu+JgDaCkbo5ikrky2c7r9v2X6DcJ+zSLphNHHLwM/0eVk5XVf1mC5HGhYpMhhg== +workbox-sw@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.0.tgz#19b47d72f598fd515fe32d2551d67bdb104434cb" + integrity sha512-uPGJ9Yost4yabnCko/IuhouquoQKrWOEqLq7L/xVYtltWe4+J8Hw8iPCVtxvXQ26hffd7MaFWUAN83j2ZWbxRg== -workbox-webpack-plugin@^6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.4.2.tgz#aad9f11b028786d5b781420e68f4e8f570ea9936" - integrity sha512-CiEwM6kaJRkx1cP5xHksn13abTzUqMHiMMlp5Eh/v4wRcedgDTyv6Uo8+Hg9MurRbHDosO5suaPyF9uwVr4/CQ== +workbox-webpack-plugin@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.0.tgz#13efad7ebbe672db6e1e6b7ebf58093b76bc0cb0" + integrity sha512-wy4uCBJELNfJVf2b4Tg3mjJQySq/aReWv4Q1RxQweJkY9ihq7DOGA3wLlXvoauek+MX/SuQfS3it+eXIfHKjvg== dependencies: fast-json-stable-stringify "^2.1.0" pretty-bytes "^5.4.1" - source-map-url "^0.4.0" upath "^1.2.0" webpack-sources "^1.4.3" - workbox-build "6.4.2" + workbox-build "6.5.0" -workbox-window@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.4.2.tgz#5319a3e343fa1e4bd15a1f53a07b58999d064c8a" - integrity sha512-KVyRKmrJg7iB+uym/B/CnEUEFG9CvnTU1Bq5xpXHbtgD9l+ShDekSl1wYpqw/O0JfeeQVOFb8CiNfvnwWwqnWQ== +workbox-window@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.0.tgz#7cc3bf4d5c7e7e0b4da579bee9e8df8bd9ba2718" + integrity sha512-DOrhiTnWup/CsNstO2uvfdKM4kdStgHd31xGGvBcoCE3Are3DRcy5s3zz3PedcAR1AKskQj3BXz0UhzQiOq8nA== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.4.2" + workbox-core "6.5.0" wrap-ansi@^2.0.0: version "2.1.0" @@ -8896,7 +9247,7 @@ yargs@^7.1.0: y18n "^3.2.1" yargs-parser "^5.0.1" -yauzl@^2.9.2: +"yauzl@2.9.2 - 2.10.0": version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=