@ -0,0 +1,55 @@
|
||||
// admin/otp.js
|
||||
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
// const multer = require('multer');
|
||||
|
||||
const { SiteController, SiteError } = require('../../../lib/site-lib');
|
||||
|
||||
class OtpAdminController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
// const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/uploads/${module.exports.slug}` });
|
||||
|
||||
const router = express.Router();
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = 'admin';
|
||||
res.locals.adminView = 'otp';
|
||||
return next();
|
||||
});
|
||||
|
||||
// router.param('otp', this.populateOtp.bind(this));
|
||||
|
||||
router.get('/', this.getIndex.bind(this));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
async getIndex (req, res, next) {
|
||||
try {
|
||||
const { otpAuth: otpAuthService } = this.dtp.services;
|
||||
if (!req.user) {
|
||||
throw new SiteError(402, "Error getting user");
|
||||
}
|
||||
res.locals.tokens = await otpAuthService.getBackupTokens(req.user, "Admin");
|
||||
res.render('admin/otp/index');
|
||||
} catch (error) {
|
||||
this.log.error('failed to get tokens', { error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'adminOtp',
|
||||
slug: 'admin-opt',
|
||||
create: async (dtp) => { return new OtpAdminController(dtp); },
|
||||
};
|
@ -0,0 +1,97 @@
|
||||
// post.js
|
||||
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
// const multer = require('multer');
|
||||
|
||||
const { SiteController, SiteError } = require('../../lib/site-lib');
|
||||
|
||||
class TagController extends SiteController {
|
||||
|
||||
constructor (dtp) {
|
||||
super(dtp, module.exports);
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
const { dtp } = this;
|
||||
// const {
|
||||
// post: postService,
|
||||
// limiter: limiterService,
|
||||
// session: sessionService,
|
||||
// } = dtp.services;
|
||||
|
||||
// const authRequired = sessionService.authCheckMiddleware({ requiredLogin: true });
|
||||
|
||||
const router = express.Router();
|
||||
dtp.app.use('/tag', router);
|
||||
|
||||
router.use(async (req, res, next) => {
|
||||
res.locals.currentView = 'home';
|
||||
return next();
|
||||
});
|
||||
|
||||
router.param('tagSlug', this.populateTagSlug.bind(this));
|
||||
|
||||
router.get('/:tagSlug', this.getSearchView.bind(this));
|
||||
}
|
||||
|
||||
async populateTagSlug (req, res, next, tagSlug) {
|
||||
const { post: postService } = this.dtp.services;
|
||||
try {
|
||||
var allPosts = false;
|
||||
var statusArray = ['published'];
|
||||
if (req.user) {
|
||||
if (req.user.flags.isAdmin) {
|
||||
statusArray.push('draft');
|
||||
allPosts = true;
|
||||
}
|
||||
}
|
||||
res.locals.allPosts = allPosts;
|
||||
res.locals.tagSlug = tagSlug;
|
||||
tagSlug = tagSlug.replace("_", " ");
|
||||
res.locals.pagination = this.getPaginationParameters(req, 20);
|
||||
res.locals.posts = await postService.getByTags(tagSlug, res.locals.pagination, statusArray);
|
||||
res.locals.tag = tagSlug;
|
||||
return next();
|
||||
|
||||
} catch (error) {
|
||||
this.log.error('failed to populate tagSlug', { tagSlug, error });
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getSearchView (req, res) {
|
||||
try {
|
||||
res.locals.pageTitle = `Tag ${res.locals.tag} on ${this.dtp.config.site.name}`;
|
||||
|
||||
res.render('tag/view');
|
||||
} catch (error) {
|
||||
this.log.error('failed to service post view', { postId: res.locals.post._id, error });
|
||||
throw SiteError("Error getting tag view:", error );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getIndex (req, res, next) {
|
||||
const { post: postService } = this.dtp.services;
|
||||
try {
|
||||
res.locals.pagination = this.getPaginationParameters(req, 20);
|
||||
res.locals.posts = await postService.getPosts(res.locals.pagination);
|
||||
res.render('tag/index');
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
slug: 'tag',
|
||||
name: 'tag',
|
||||
create: async (dtp) => {
|
||||
let controller = new TagController(dtp);
|
||||
return controller;
|
||||
},
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
// media-router.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const STATUS_LIST = [
|
||||
'starting', // the router process is starting and configuring itself
|
||||
'active', // the router is active and available for service
|
||||
'capacity', // the router is at or over capacity
|
||||
'closing', // the router is closing/shutting down
|
||||
'closed', // the router no longer exists
|
||||
];
|
||||
|
||||
const RouterHostSchema = new Schema({
|
||||
address: { type: String, required: true, index: 1 },
|
||||
port: { type: Number, required: true },
|
||||
});
|
||||
|
||||
/*
|
||||
* A Router is a "multi-user conference call instance" somewhere on the
|
||||
* infrastructure. This model helps us manage them, balance load across them,
|
||||
* and route calls to and between them (for scale).
|
||||
*
|
||||
* These records are created when a call is being created, and are commonly
|
||||
* left in the database after all call participants have left. An expires index
|
||||
* is used to sweep up router records after 30 days. This allows us to perform
|
||||
* statistics aggregation on router use and store aggregated results as part of
|
||||
* long-term reporting.
|
||||
*/
|
||||
const MediaRouterSchema = new Schema({
|
||||
created: { type: Date, default: Date.now, required: true, expires: '30d' },
|
||||
lastActivity: { type: Date, default: Date.now, required: true },
|
||||
status: { type: String, enum: STATUS_LIST, default: 'starting', required: true, index: true },
|
||||
name: { type: String },
|
||||
description: { type: String },
|
||||
access: {
|
||||
isPrivate: { type: Boolean, default: true, required: true },
|
||||
passcodeHash: { type: String, select: false },
|
||||
},
|
||||
host: { type: RouterHostSchema, required: true, select: false },
|
||||
stats: {
|
||||
routerCount: { type: Number, default: 0, required: true },
|
||||
consumerCount: { type: Number, default: 0, required: true },
|
||||
producerCount: { type: Number, default: 0, required: true },
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('MediaRouter', MediaRouterSchema);
|
@ -1,43 +0,0 @@
|
||||
// media-worker.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// License: Apache-2.0
|
||||
|
||||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const STATUS_LIST = [
|
||||
'starting', // the router process is starting and configuring itself
|
||||
'active', // the router is active and available for service
|
||||
'capacity', // the router is at or over capacity
|
||||
'closing', // the router is closing/shutting down
|
||||
'closed', // the router no longer exists
|
||||
];
|
||||
|
||||
const WebRtcListenSchema = new Schema({
|
||||
protocol: { type: String, enum: ['tcp','udp'], required: true },
|
||||
ip: { type: String, required: true },
|
||||
port: { type: Number, required: true },
|
||||
});
|
||||
|
||||
/*
|
||||
* A media worker is a host process with one or more MediaRouter instances
|
||||
* processing multi-user conference calls.
|
||||
*/
|
||||
const MediaWorkerSchema = new Schema({
|
||||
created: { type: Date, default: Date.now, required: true, expires: '30d' },
|
||||
lastActivity: { type: Date, default: Date.now, required: true },
|
||||
status: { type: String, enum: STATUS_LIST, default: 'starting', required: true, index: true },
|
||||
webRtcServer: {
|
||||
listenInfos: { type: [WebRtcListenSchema] },
|
||||
},
|
||||
stats: {
|
||||
routerCount: { type: Number, default: 0, required: true },
|
||||
consumerCount: { type: Number, default: 0, required: true },
|
||||
producerCount: { type: Number, default: 0, required: true },
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('MediaWorker', MediaWorkerSchema);
|
@ -0,0 +1,51 @@
|
||||
mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaultImage, currentImage, cropperOptions)
|
||||
div(id= containerId).dtp-file-upload
|
||||
form(method="POST", action= actionUrl, enctype="multipart/form-data", onsubmit= "return dtp.app.submitImageForm(event);").uk-form
|
||||
.uk-margin
|
||||
.uk-card.uk-card-default.uk-card-small
|
||||
.uk-card-body
|
||||
div(uk-grid).uk-flex-middle.uk-flex-center
|
||||
div(class="uk-width-1-1 uk-width-auto@m")
|
||||
.upload-image-container.size-512
|
||||
if !!currentImage
|
||||
img(id= imageId, src= currentImage.path, class= imageClass).sb-large
|
||||
else
|
||||
img(id= imageId, src= defaultImage, class= imageClass)
|
||||
|
||||
div(class="uk-width-1-1 uk-width-auto@m")
|
||||
.uk-text-small.uk-margin
|
||||
#file-select
|
||||
.uk-margin(class="uk-text-center uk-text-left@m")
|
||||
span.uk-text-middle Select an image
|
||||
div(uk-form-custom).uk-margin-small-left
|
||||
input(
|
||||
type="file",
|
||||
formenctype="multipart/form-data",
|
||||
accept=".jpg,.png,image/jpeg,image/png",
|
||||
data-file-select-container= containerId,
|
||||
data-file-select="test-image-upload",
|
||||
data-file-size-element= "file-size",
|
||||
data-file-max-size= 15 * 1024000,
|
||||
data-image-id= imageId,
|
||||
data-cropper-options= cropperOptions,
|
||||
onchange="return dtp.app.selectImageFile(event);",
|
||||
)
|
||||
button(type="button", tabindex="-1").uk-button.uk-button-default Select
|
||||
|
||||
#file-info(class="uk-text-center uk-text-left@m", hidden)
|
||||
#file-name.uk-text-bold
|
||||
if currentImage
|
||||
div resolution: #[span#image-resolution-w= numeral(currentImage.metadata.width).format('0,0')]x#[span#image-resolution-h= numeral(currentImage.metadata.height).format('0,0')]
|
||||
div size: #[span#file-size= numeral(currentImage.metadata.size).format('0,0.00b')]
|
||||
div last modified: #[span#file-modified= moment(currentImage.created).format('MMM DD, YYYY')]
|
||||
else
|
||||
div resolution: #[span#image-resolution-w 512]x#[span#image-resolution-h 512]
|
||||
div size: #[span#file-size N/A]
|
||||
div last modified: #[span#file-modified N/A]
|
||||
|
||||
.uk-card-footer
|
||||
div(class="uk-flex-center", uk-grid)
|
||||
#file-save-btn(hidden).uk-width-auto
|
||||
button(
|
||||
type="submit",
|
||||
).uk-button.uk-button-primary Save
|
@ -0,0 +1,28 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
h1.margin-remove Tokens
|
||||
section.uk-section.uk-section-default.uk-section-xsmall
|
||||
.uk-container
|
||||
.uk-text-small
|
||||
h4 This is where you will regenerate OTP tokens for your admin account and destroy your old OTP account.
|
||||
//- .uk-width-auto
|
||||
button(
|
||||
type="button",
|
||||
data-user= user._id,
|
||||
onclick="return dtp.adminApp.generateOTPTokens(event);",
|
||||
).uk-button.dtp-button-danger
|
||||
+renderButtonIcon('fa-repeat', 'Generate OTP Tokens')
|
||||
//- regenerate route should set this so tokens can be viewed once.
|
||||
if otpRegen
|
||||
section.uk-section.uk-section-default.uk-section-xsmall
|
||||
.uk-container
|
||||
h3 You should save these tokens in a safe place. This is the only time you will see them.
|
||||
p These tokens should be saved in a safe place so you can get into your account should you lose your 2FA device
|
||||
each token of tokens
|
||||
ul.uk-list.uk-list-divider
|
||||
li
|
||||
.uk-text-small= token.token
|
@ -0,0 +1,47 @@
|
||||
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
|
||||
|
||||
//- h2 Add or replace your site images here
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
fieldset
|
||||
legend Site Icon
|
||||
.uk-margin
|
||||
if siteIcon
|
||||
p.uk-card-title Replace your site icon below.
|
||||
else
|
||||
p.uk-card-title You do not currently have a site icon. Add one below.
|
||||
+renderFileUploadImage(
|
||||
`/admin/settings/images/updateSiteIcon`,
|
||||
'site-icon-upload',
|
||||
'site-icon-file',
|
||||
'site-icon-picture',
|
||||
`/img/icon/dtp-sites.png`,
|
||||
siteIcon,
|
||||
{ aspectRatio: 1 },
|
||||
)
|
||||
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
fieldset
|
||||
legend Default poster
|
||||
.uk-margin
|
||||
if postImage
|
||||
p.uk-card-title Replace your default post image below.
|
||||
else
|
||||
p.uk-card-title You do not currently have a default post image. Add one below.
|
||||
+renderFileUploadImage(
|
||||
`/admin/settings/images/updatePostImage`,
|
||||
'site-post-upload',
|
||||
'site-post-file',
|
||||
'site-post-picture',
|
||||
`/img/default-poster.jpg`,
|
||||
postImage,
|
||||
{ aspectRatio: 16/9 },
|
||||
)
|
@ -0,0 +1,64 @@
|
||||
mixin renderPostDraftList (posts)
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each draft in posts
|
||||
li
|
||||
a(href=`/post/${draft.slug}`, title="Preview draft")= draft.title
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-flex-middle
|
||||
.uk-width-expand
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-text-small
|
||||
.uk-width-expand
|
||||
a(href=`/post/${draft.slug}`, title="Edit draft")= moment(draft.created).fromNow()
|
||||
if drafts.all
|
||||
span by
|
||||
a(href=`/user/${draft.author.username}`)=` ${draft.author.username}`
|
||||
.uk-width-auto
|
||||
a(href=`/post/${draft._id}/edit`).uk-display-block
|
||||
+renderButtonIcon('fa-pen', 'edit')
|
||||
|
||||
.uk-width-auto
|
||||
a(
|
||||
href="",
|
||||
title="Delete draft",
|
||||
data-post-id= draft._id,
|
||||
data-post-title= draft.title,
|
||||
onclick="return dtp.app.deletePost(event);",
|
||||
).uk-text-danger
|
||||
+renderButtonIcon('fa-trash', 'delete')
|
||||
else
|
||||
.uk-margin-small You have no drafts.
|
||||
|
||||
mixin renderFullDraftList (posts)
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each draft in posts
|
||||
li
|
||||
a(href=`/post/${draft.slug}`, title="Preview draft")= draft.title
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-medium.uk-flex-middle
|
||||
.uk-width-expand
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-medium.uk-text-medium
|
||||
.uk-width-expand
|
||||
a(href=`/post/${draft.slug}`, title="Edit draft")= moment(draft.created).fromNow()
|
||||
if drafts.all
|
||||
span by
|
||||
a(href=`/user/${draft.author.username}`)=` ${draft.author.username}`
|
||||
.uk-width-auto
|
||||
a(href=`/post/${draft._id}/edit`).uk-display-block
|
||||
+renderButtonIcon('fa-pen', 'edit')
|
||||
|
||||
.uk-width-auto
|
||||
a(
|
||||
href="",
|
||||
title="Delete draft",
|
||||
data-post-id= draft._id,
|
||||
data-post-title= draft.title,
|
||||
onclick="return dtp.app.deletePost(event);",
|
||||
).uk-text-danger
|
||||
+renderButtonIcon('fa-trash', 'delete')
|
||||
+renderPaginationBar('/author/posts', drafts.totalPostCount)
|
||||
else
|
||||
.uk-margin-small You have no drafts.
|
@ -0,0 +1,24 @@
|
||||
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-width-1-1 uk-width-1-3@s uk-flex-first", class={
|
||||
'uk-flex-first@m': ((postIndex % postIndexModulus) === 0),
|
||||
'uk-flex-last@m': ((postIndex % postIndexModulus) !== 0),
|
||||
})
|
||||
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@s', class="uk-flex-last", class={
|
||||
'uk-flex-first@m': ((postIndex % postIndexModulus) !== 0),
|
||||
'uk-flex-last@m': ((postIndex % postIndexModulus) === 0),
|
||||
})
|
||||
article.uk-article
|
||||
h4(style="line-height: 1.1;").uk-article-title.uk-margin-small= post.title
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small
|
||||
.uk-width-auto author: #{post.author.displayName || post.author.username}
|
||||
.uk-width-auto
|
||||
span published: #{moment(post.created).format("MMM DD YYYY HH:MM a")}
|
@ -0,0 +1,70 @@
|
||||
mixin renderPostList (posts)
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each post in posts
|
||||
li
|
||||
a(href=`/post/${post.slug}`).uk-display-block
|
||||
div= post.title
|
||||
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-text-small
|
||||
.uk-width-expand
|
||||
a(href=`/post/${post.slug}`)= moment(post.created).fromNow()
|
||||
if posts.all
|
||||
span by
|
||||
a(href=`/user/${post.author.username}`)=` ${post.author.username}`
|
||||
.uk-width-auto
|
||||
a(href=`/post/${post._id}/edit`).uk-display-block
|
||||
+renderButtonIcon('fa-pen', 'edit')
|
||||
|
||||
.uk-width-auto
|
||||
a(
|
||||
href="",
|
||||
data-post-id= post._id,
|
||||
data-post-title= post.title,
|
||||
onclick="return dtp.app.deletePost(event);",
|
||||
).uk-display-block.uk-text-danger
|
||||
+renderButtonIcon('fa-trash', 'delete')
|
||||
|
||||
div(style="width: 65px;")
|
||||
span
|
||||
i.fas.fa-eye
|
||||
span.uk-margin-small-left= formatCount(post.stats.totalVisitCount)
|
||||
else
|
||||
.uk-margin-small There are no posts.
|
||||
|
||||
mixin renderPublishedPostList (posts)
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each post in posts
|
||||
li
|
||||
a(href=`/post/${post.slug}`).uk-display-block
|
||||
div= post.title
|
||||
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-text-small
|
||||
.uk-width-expand
|
||||
a(href=`/post/${post.slug}`)= moment(post.created).fromNow()
|
||||
if posts.all
|
||||
span by
|
||||
a(href=`/user/${post.author.username}`)=` ${post.author.username}`
|
||||
.uk-width-auto
|
||||
a(href=`/post/${post._id}/edit`).uk-display-block
|
||||
+renderButtonIcon('fa-pen', 'edit')
|
||||
|
||||
.uk-width-auto
|
||||
a(
|
||||
href="",
|
||||
data-post-id= post._id,
|
||||
data-post-title= post.title,
|
||||
onclick="return dtp.app.deletePost(event);",
|
||||
).uk-display-block.uk-text-danger
|
||||
+renderButtonIcon('fa-trash', 'delete')
|
||||
|
||||
div(style="width: 65px;")
|
||||
span
|
||||
i.fas.fa-eye
|
||||
span.uk-margin-small-left= formatCount(post.stats.totalVisitCount)
|
||||
+renderPaginationBar('/author/posts', published.totalPostCount)
|
||||
else
|
||||
.uk-margin-small No published posts.
|
@ -0,0 +1,28 @@
|
||||
extends ../../layouts/main
|
||||
block content
|
||||
|
||||
include ../components/draft-list
|
||||
include ../components/list
|
||||
include ../../post/components/summary
|
||||
|
||||
include ../../components/pagination-bar
|
||||
|
||||
section.uk-section.uk-section-default.uk-section-xsmall
|
||||
.uk-container.uk-container-expand
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
h2.uk-margin-remove Drafts
|
||||
.uk-width-auto
|
||||
if user.permissions.canAuthorPosts || user.flags.isAdmin
|
||||
a(href= "/post/compose").uk-button.uk-button-primary.uk-border-rounded
|
||||
span
|
||||
i.fas.fa-plus
|
||||
span.uk-margin-small-left.uk-text-bold Create Post
|
||||
.uk-width-medium
|
||||
a(href= "/author").uk-button.uk-button-primary.uk-border-rounded
|
||||
span.uk-margin-small-middle.uk-text-bold Author Dashboard
|
||||
div(uk-grid)
|
||||
div(class="uk-width-1-1 uk-width-3-3@m")
|
||||
+renderSectionTitle('Drafts')
|
||||
+renderFullDraftList(drafts.posts)
|
||||
|
@ -1,58 +1,27 @@
|
||||
extends ../../layouts/main
|
||||
block content
|
||||
|
||||
include ../../post/components/draft-list
|
||||
include ../../post/components/list
|
||||
include ../components/draft-list
|
||||
include ../components/list
|
||||
include ../../post/components/summary
|
||||
|
||||
include ../../components/pagination-bar
|
||||
|
||||
section.uk-section.uk-section-default.uk-section-small
|
||||
section.uk-section.uk-section-default.uk-section-xsmall
|
||||
.uk-container.uk-container-expand
|
||||
h1 Post Author Dashboard
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
h2.uk-margin-remove Published Posts
|
||||
if user.permissions.canAuthorPosts
|
||||
.uk-width-auto
|
||||
a(href= "/post/compose").uk-button.uk-button-primary.uk-border-rounded
|
||||
span
|
||||
i.fas.fa-plus
|
||||
span.uk-margin-small-left.uk-text-bold Create Post
|
||||
.uk-width-medium
|
||||
a(href= "/author").uk-button.uk-button-primary.uk-border-rounded
|
||||
span.uk-margin-small-left.uk-text-bold Author Dashboard
|
||||
div(uk-grid)
|
||||
.uk-width-2-3
|
||||
.uk-margin
|
||||
+renderSectionTitle('Your Posts')
|
||||
.content-block
|
||||
if published && Array.isArray(published.posts) && (published.posts.length > 0)
|
||||
.uk-margin
|
||||
ul.uk-list.uk-list-divider
|
||||
each post in published.posts
|
||||
li
|
||||
a(href=`/post/${post.slug}`).uk-display-block
|
||||
div= post.title
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-text-small
|
||||
.uk-width-expand
|
||||
a(href=`/post/${post.slug}`)= moment(post.created).fromNow()
|
||||
|
||||
.uk-width-auto
|
||||
a(href=`/post/${post._id}/edit`).uk-display-block
|
||||
+renderButtonIcon('fa-pen', 'edit')
|
||||
|
||||
.uk-width-auto
|
||||
a(
|
||||
href="",
|
||||
data-post-id= post._id,
|
||||
data-post-title= post.title,
|
||||
onclick="return dtp.app.deletePost(event);",
|
||||
).uk-display-block.uk-text-danger
|
||||
+renderButtonIcon('fa-trash', 'delete')
|
||||
|
||||
div(style="width: 65px;")
|
||||
span
|
||||
i.fas.fa-eye
|
||||
span.uk-margin-small-left= formatCount(post.stats.totalVisitCount)
|
||||
+renderPaginationBar('/author/post', published.totalPostCount)
|
||||
else
|
||||
div You have no published posts.
|
||||
|
||||
.uk-width-1-3
|
||||
.uk-margin
|
||||
+renderSectionTitle('Your Drafts')
|
||||
+renderPostDraftList(drafts.posts)
|
||||
|
||||
.uk-margin
|
||||
+renderSectionTitle('Archived')
|
||||
+renderPostList(archive.posts)
|
||||
div(class="uk-width-1-1 uk-width-3-3@m")
|
||||
+renderSectionTitle('Posts')
|
||||
+renderPublishedPostList(published.posts)
|
@ -1,5 +1,5 @@
|
||||
block twitter-card
|
||||
meta(name='twitter:card', content='summary_large_image')
|
||||
meta(name='twitter:image' content= `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
|
||||
meta(name='twitter:image' content= shareImage || `https://${site.domain}/img/social-cards/${site.domainKey}.png?v=${pkg.version}`)
|
||||
meta(name='twitter:title', content= pageTitle || site.name)
|
||||
meta(name='twitter:description', content= pageDescription || site.description)
|
||||
|
@ -1,27 +0,0 @@
|
||||
extends ../layouts/main
|
||||
block content
|
||||
|
||||
include ../components/pagination-bar
|
||||
|
||||
section.uk-section.uk-section-default
|
||||
.uk-container
|
||||
|
||||
article.uk-article
|
||||
.uk-margin
|
||||
h1.uk-article-title.uk-margin-remove #{site.name} News Feed
|
||||
.uk-text-bold #{formatCount(newsroom.totalFeedEntryCount)} articles indexed by #{site.name} in one chronological feed.
|
||||
|
||||
.uk-margin
|
||||
if Array.isArray(newsroom.entries) && (newsroom.entries.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each entry in newsroom.entries
|
||||
li
|
||||
.uk-text-large.uk-text-bold.uk-margin-small
|
||||
a(href= entry.link, target="shing_reader")= entry.title
|
||||
.uk-margin-small= entry.description
|
||||
.uk-text-small source: #[a(href= entry.feed.link, target="_blank")= entry.feed.title]
|
||||
else
|
||||
div There are no news feed entries.
|
||||
|
||||
.uk-margin
|
||||
+renderPaginationBar(`/newsroom/feed`, newsroom.totalFeedEntryCount)
|
@ -0,0 +1,16 @@
|
||||
extends ../../layouts/main-sidebar
|
||||
block content
|
||||
|
||||
include ../../components/pagination-bar
|
||||
include components/credit
|
||||
|
||||
div(uk-grid).uk-flex-expand
|
||||
.uk-width-expand
|
||||
h3.uk-margin-remove= `Author List`
|
||||
if Array.isArray(authors) && (authors.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each author in authors
|
||||
li
|
||||
+renderAuthorCredit(author)
|
||||
.uk-card-footer
|
||||
+renderPaginationBar(`/post/authors`, totalAuthorCount )
|
@ -0,0 +1,18 @@
|
||||
mixin renderAuthorCredit (author)
|
||||
div(uk-grid).uk-grid-small
|
||||
.uk-width-auto
|
||||
+renderProfileIcon(author)
|
||||
.uk-width-expand
|
||||
.uk-margin-small
|
||||
div(uk-grid).uk-flex-middle
|
||||
.uk-width-expand
|
||||
- var userUrl = !!author.coreUserId ? `/user/core/${author._id}` : `/user/${author.username}`;
|
||||
a(href= userUrl, title="View member profile")
|
||||
.uk-text-bold(style="line-height: 1em;")= author.displayName || author.username
|
||||
.uk-width-auto
|
||||
.uk-text-small.uk-text-muted(style="line-height: 1em;")
|
||||
if author.coreUserId
|
||||
a(href=`${process.env.DTP_CORE_AUTH_SCHEME}://${author.core.meta.domain}/user/${author.coreUserId}`)= author.core.meta.name
|
||||
else if !Array.isArray(posts)
|
||||
a(href= `/post/author/${author.username}`)= `View posts by author`
|
||||
.uk-text-small= author.bio
|
@ -0,0 +1,15 @@
|
||||
mixin renderPostSummaryFull (post)
|
||||
div(uk-grid).uk-grid-small
|
||||
if post.image
|
||||
.uk-width-auto
|
||||
img(src= `/image/${post.image._id}`).uk-width-small
|
||||
else
|
||||
.uk-width-auto
|
||||
img(src="/img/default-poster.jpg").uk-width-small
|
||||
.uk-width-expand
|
||||
.uk-text-large.uk-text-bold(style="line-height: 1em;")
|
||||
a(href=`/post/${post.slug}`)= `${post.title}`
|
||||
.uk-text-small.uk-text-muted
|
||||
div
|
||||
div= moment(post.created).fromNow()
|
||||
div= post.summary
|
@ -0,0 +1,16 @@
|
||||
extends ../../layouts/main-sidebar
|
||||
block content
|
||||
|
||||
include ../../components/pagination-bar
|
||||
include components/list
|
||||
|
||||
div(uk-grid).uk-flex-expand
|
||||
.uk-width-expand
|
||||
h3.uk-margin-remove= `Posts by ${author.username}`
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
ul.uk-list.uk-list-divider
|
||||
each post in posts
|
||||
li
|
||||
+renderPostSummaryFull(post)
|
||||
.uk-card-footer
|
||||
+renderPaginationBar(`/post/author/${author.username}`, posts.length )
|
@ -0,0 +1,19 @@
|
||||
mixin renderPostSummaryFull (post)
|
||||
div(uk-grid).uk-grid-small
|
||||
if post.image
|
||||
.uk-width-auto
|
||||
img(src= `/image/${post.image._id}`).uk-width-medium
|
||||
else
|
||||
.uk-width-auto
|
||||
img(src="/img/default-poster.jpg").uk-width-medium
|
||||
.uk-width-expand
|
||||
.uk-text-large.uk-text-bold(style="line-height: 1em;")
|
||||
a(href=`/post/${post.slug}`)= `${post.title}`
|
||||
.uk-text-small.uk-text-muted
|
||||
div
|
||||
div= moment(post.created).fromNow()
|
||||
span by
|
||||
a(href=`/user/${post.author.username}`)=` ${post.author.username}`
|
||||
if user && allPosts
|
||||
div= `Status: ${post.status}`
|
||||
div= post.summary
|
@ -0,0 +1,37 @@
|
||||
extends ../layouts/main-sidebar
|
||||
|
||||
include components/list
|
||||
|
||||
include ../components/pagination-bar
|
||||
|
||||
block content
|
||||
|
||||
|
||||
if Array.isArray(posts) && (posts.length > 0)
|
||||
h3= `Posts with the tag ${tag}.`
|
||||
ul.uk-list.uk-list-divider
|
||||
each post in posts
|
||||
li
|
||||
+renderPostSummaryFull(post)
|
||||
|
||||
.uk-card-footer
|
||||
+renderPaginationBar(`/tag/${tagSlug}`, posts.length )
|
||||
//- li
|
||||
if post.image
|
||||
img(src= `/image/${post.image._id}`, href=`/post/${post.slug}`, style="max-height: 350px; object-fit: cover; vertical-align:middle;margin:0px 20px;").responsive
|
||||
else
|
||||
img(src="/img/default-poster.jpg", href=`/post/${post.slug}`, style="max-height: 350px; object-fit: cover; vertical-align:middle;margin:0px 20px;").responsive
|
||||
a(href=`/post/${post.slug}`).uk-display-block
|
||||
div.h2= post.title
|
||||
|
||||
.uk-article-meta
|
||||
div(uk-grid).uk-grid-small.uk-text-small
|
||||
.uk-width-expand
|
||||
a(href=`/post/${post.slug}`)= moment(post.created).fromNow()
|
||||
span by
|
||||
a(href=`/user/${post.author.username}`)=` ${post.author.username}`
|
||||
.uk-width-expand
|
||||
div= post.summary
|
||||
|
||||
else
|
||||
h3= `There are no posts with the tag ${tag}.`
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 942 B |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
git pull prod master
|
||||
yarn --production=false
|
||||
gulp build
|
||||
|
||||
./restart-production
|
@ -1,178 +0,0 @@
|
||||
// dtp-media-engine.js
|
||||
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
// All Rights Reserved
|
||||
|
||||
'use strict';
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const mediasoup = require('mediasoup');
|
||||
|
||||
const { SiteAsync, SiteCommon, SitePlatform, SiteLog } = require(path.join(__dirname, 'lib', 'site-lib'));
|
||||
|
||||
module.rootPath = __dirname;
|
||||
module.pkg = require(path.join(module.rootPath, 'package.json'));
|
||||
module.config = {
|
||||
component: { name: 'dtpMediaEngine', slug: 'dtp-media-engine' },
|
||||
root: module.rootPath,
|
||||
site: require(path.join(module.rootPath, 'config', 'site')),
|
||||
webRtcServer: [
|
||||
{
|
||||
protocol: 'udp',
|
||||
ip: process.env.MEDIASOUP_WEBRTC_BIND_ADDR || '127.0.0.1',
|
||||
port: process.env.MEDIASOUP_WEBRTC_BIND_PORT || 20000,
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
module.log = new SiteLog(module, module.config.component);
|
||||
|
||||
class MediaEngineWorker extends SiteCommon {
|
||||
|
||||
constructor ( ) {
|
||||
super(module, { name: 'dtpMediaWorker', slug: 'dtp-media-worker' });
|
||||
this._id = mongoose.Types.ObjectId();
|
||||
}
|
||||
|
||||
async start ( ) {
|
||||
await super.start();
|
||||
|
||||
try {
|
||||
this.worker = await mediasoup.createWorker({
|
||||
logLevel: 'warn',
|
||||
dtlsCertificateFile: process.env.HTTPS_SSL_CRT,
|
||||
dtlsPrivateKeyFile: process.env.HTTPS_SSL_KEY,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`failed to start mediasoup worker process: ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const BIND_PORT = 20000 + module.nextWorkerIdx++;
|
||||
this.webRtcServer = await this.worker.createWebRtcServer({
|
||||
listenInfos: [
|
||||
{
|
||||
protocol: 'udp',
|
||||
ip: '127.0.0.1',
|
||||
port: BIND_PORT,
|
||||
},
|
||||
{
|
||||
protocol: 'tcp',
|
||||
ip: '127.0.0.1',
|
||||
port: BIND_PORT,
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`failed to start mediasoup WebRTC Server: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stop ( ) {
|
||||
if (this.webRtcServer && !this.webRtcServer.closed) {
|
||||
this.log.info('closing mediasoup WebRTC server');
|
||||
this.webRtcServer.close();
|
||||
delete this.webRtcServer;
|
||||
}
|
||||
|
||||
if (this.worker && !this.worker.closed) {
|
||||
this.log.info('closing mediasoup worker process');
|
||||
this.worker.close();
|
||||
delete this.worker;
|
||||
}
|
||||
|
||||
await super.stop();
|
||||
}
|
||||
}
|
||||
|
||||
module.onNewWorker = async (worker) => {
|
||||
module.log.info('new worker created', { worker: worker.pid });
|
||||
worker.observer.on('close', ( ) => {
|
||||
module.log.info('worker shutting down', { worker: worker.pid });
|
||||
});
|
||||
|
||||
worker.observer.on('newrouter', (router) => {
|
||||
module.log.info('new router created', { worker: worker.pid, router: router.id });
|
||||
router.observer.on('close', ( ) => {
|
||||
module.log.info('router shutting down', { worker: worker.pid, router: router.id });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.createWorker = async ( ) => {
|
||||
const worker = new MediaEngineWorker();
|
||||
module.workers.push(worker);
|
||||
await worker.start();
|
||||
};
|
||||
|
||||
module.shutdown = async ( ) => {
|
||||
await SiteAsync.each(module.workers, async (worker) => {
|
||||
try {
|
||||
await worker.stop();
|
||||
} catch (error) {
|
||||
module.log.error('failed to stop worker', { error });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* SERVER PROCESS INIT
|
||||
*/
|
||||
|
||||
(async ( ) => {
|
||||
|
||||
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...');
|
||||
await module.shutdown();
|
||||
const exitCode = await SitePlatform.shutdown();
|
||||
process.nextTick(( ) => {
|
||||
process.exit(exitCode);
|
||||
});
|
||||
});
|
||||
|
||||
process.once('SIGUSR2', async ( ) => {
|
||||
await SitePlatform.shutdown();
|
||||
process.kill(process.pid, 'SIGUSR2');
|
||||
});
|
||||
|
||||
try {
|
||||
await SitePlatform.startPlatform(module);
|
||||
} catch (error) {
|
||||
module.log.error(`failed to start DTP ${module.config.component.slug} process`, { error });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
module.log.info('registering mediasoup observer callbacks');
|
||||
mediasoup.observer.on('newworker', module.onNewWorker);
|
||||
|
||||
module.log.info('creating mediasoup worker instance');
|
||||
|
||||
module.nextWorkerIdx = 0;
|
||||
module.workers = [ ];
|
||||
|
||||
await module.createWorker();
|
||||
|
||||
module.log.info('DTP Media Engine online');
|
||||
} catch (error) {
|
||||
module.log.error('failed to start DTP Media Engine', { error });
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
})();
|