Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into moreStuff

master
Andrew Woodlee 2 years ago
commit 4151404877

@ -90,6 +90,7 @@ class AdminController extends SiteController {
};
res.locals.channels = await venueService.getChannels();
res.locals.pageTitle = `Admin Dashbord for ${this.dtp.config.site.name}`;
res.render('admin/index');
}

@ -25,57 +25,20 @@ class OtpAdminController extends SiteController {
return next();
});
router.param('user', this.populateUser.bind(this));
// router.param('otp', this.populateOtp.bind(this));
router.get('/', this.getIndex.bind(this));
router.post('/generate/:user', this.regenerateOTPTokens.bind(this));
return router;
}
async populateUser (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.getOTPAccount(req.user, "Admin");
if (!res.locals.tokens) {
throw new SiteError(402, "Error getting OTP details for user");
}
return next();
} catch (error) {
this.log.error('failed to get tokens', { error });
return next(error);
}
}
async regenerateOTPTokens (req, res, next) {
try {
const { otpAuth: otpAuthService } = this.dtp.services;
if (!req.user._id.equals(res.locals.tokens.user)) {
throw new SiteError(402, "This is not your account.");
}
res.locals.generate = otpAuthService.generateBackupTokens(req.user, 'Admin');
res.reload();
} catch (error) {
this.log.error('failed to get tokens', { error });
return next(error);
}
}
async getIndex (req, res, next) {
try {
const { otpAuth: otpAuthService } = this.dtp.services;
if (!req.user) {
throw new SiteError(402, "Error getting user");
}
const tokens = await otpAuthService.getOTPAccount(req.user, "Admin");
res.locals.tokens = tokens.backupTokens;
res.locals.tokens = await otpAuthService.getBackupTokens(req.user, "Admin");
res.render('admin/otp/index');
} catch (error) {
this.log.error('failed to get tokens', { error });

@ -16,6 +16,13 @@ class SettingsController extends SiteController {
async start ( ) {
const router = express.Router();
const imageUpload = this.createMulter('uploads', {
limits: {
fileSize: 1024 * 1000 * 12,
},
});
router.use(async (req, res, next) => {
res.locals.currentView = 'admin';
res.locals.adminView = 'settings';
@ -24,8 +31,12 @@ class SettingsController extends SiteController {
router.post('/', this.postUpdateSettings.bind(this));
router.post('/images/updateSiteIcon', imageUpload.single('imageFile'), this.postUpdateSiteIcon.bind(this));
router.get('/', this.getSettingsView.bind(this));
router.get('/images', this.getImageSettings.bind(this));
return router;
}
@ -47,6 +58,43 @@ class SettingsController extends SiteController {
return next(error);
}
}
async getImageSettings (req, res, next) {
const { image: imageService } = this.dtp.services;
res.locals.adminView = 'image-settings';
res.locals.pageTitle = `Image settings for ${this.dtp.config.site.name}`;
try {
res.locals.siteIcon = await imageService.getSiteIconInfo();
res.render('admin/settings/images');
} catch (error) {
return next(error);
}
}
async postUpdateSiteIcon (req, res) {
const { image: imageService } = this.dtp.services;
try {
const displayList = this.createDisplayList('site-icon');
await imageService.updateSiteIcon(req.body, req.file);
displayList.showNotification(
'Site Icon updated successfully.',
'success',
'bottom-center',
2000,
);
res.status(200).json({
success: true,
displayList,
});
} catch (error) {
this.log.error('failed to update site icon', { error });
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
});
}
}
}
module.exports = {

@ -77,8 +77,6 @@ class HomeController extends SiteController {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getPosts(res.locals.pagination);
res.locals.newsfeed = await feedService.getNewsfeed();
res.render('index');
} catch (error) {
this.log.error('failed to render home view', { error });

@ -33,12 +33,12 @@ class UserController extends SiteController {
const otpSetup = otpAuthService.middleware('Account', {
adminRequired: false,
otpRequired: true,
otpRedirectURL: async (req) => { return `/user/${req.user._id}`; },
otpRedirectURL: async (req) => { return `/user/${req.user.username}`; },
});
const otpMiddleware = otpAuthService.middleware('Account', {
adminRequired: false,
otpRequired: false,
otpRedirectURL: async (req) => { return `/user/${req.user._id}`; },
otpRedirectURL: async (req) => { return `/user/${req.user.username}`; },
});
router.use(
@ -218,7 +218,7 @@ class UserController extends SiteController {
if (error) {
return next(error);
}
res.redirect(`/user/${res.locals.user._id}`);
res.redirect(`/user/${res.locals.user.username}`);
});
} catch (error) {
this.log.error('failed to create new user', { error });

@ -30,6 +30,17 @@ class FeedService extends SiteService {
this.jobQueue = await this.getJobQueue('newsroom', this.dtp.config.jobQueues.newsroom);
}
middleware (options) {
options = Object.assign({ maxEntryCount: 5 }, options);
return async (req, res, next) => {
if (this.isSystemRoute(req.path)) {
return next(); // don't load newsfeeds for non-content routes
}
res.locals.newsfeed = await this.getNewsfeed({ skip: 0, cpp: options.maxEntryCount });
return next();
};
}
async create (feedDefinition) {
feedDefinition.url = feedDefinition.url.trim();
const feedContent = await this.load(feedDefinition.url);

@ -37,7 +37,7 @@ class ImageService extends SiteService {
try {
this.log.debug('processing uploaded image', { imageDefinition, file });
const sharpImage = await sharp(file.path);
const sharpImage = sharp(file.path);
const metadata = await sharpImage.metadata();
// create an Image model instance, but leave it here in application memory.
@ -216,6 +216,72 @@ class ImageService extends SiteService {
await SiteAsync.each(outputs, processOutputImage, 4);
}
async getSiteIconInfo() {
const siteDomain = this.dtp.config.site.domainKey;
const siteImagesDir = path.join(this.dtp.config.root, 'client', 'img');
const siteIconDir = path.join(siteImagesDir, 'icon', siteDomain);
let icon;
try {
await fs.promises.access(siteIconDir);
const iconMetadata = await sharp(path.join(siteIconDir, 'icon-512x512.png')).metadata();
icon = {
metadata: iconMetadata,
path: `/img/icon/${siteDomain}/icon-512x512.png`,
};
} catch (error) {
icon = null;
}
return icon;
}
async updateSiteIcon(imageDefinition, file) {
this.log.debug('updating site icon', { imageDefinition, file });
try {
const siteDomain = this.dtp.config.site.domainKey;
const siteImagesDir = path.join(this.dtp.config.root, 'client', 'img');
const siteIconDir = path.join(siteImagesDir, 'icon', siteDomain);
const sourceIconFilePath = file.path;
const sizes = [16, 32, 36, 48, 57, 60, 70, 72, 76, 96, 114, 120, 144, 150, 152, 180, 192, 256, 310, 384, 512];
await fs.promises.mkdir(siteIconDir, { force: true, recursive: true });
for (var size of sizes) {
await sharp(sourceIconFilePath).resize({
fit: sharp.fit.contain,
width: size,
height: size,
}).png()
.toFile(path.join(siteIconDir, `icon-${size}x${size}.png`));
}
await fs.promises.cp(sourceIconFilePath, path.join(siteIconDir, `${siteDomain}.png`));
await fs.promises.cp(sourceIconFilePath, path.join(siteImagesDir, 'social-cards', `${siteDomain}.png`));
return path.join(siteIconDir, 'icon-512x512.png');
} catch (error) {
this.log.error('failed to update site icon', { error });
throw error;
} finally {
this.log.info('removing uploaded image from local file system', { file: file.path });
await fs.promises.rm(file.path);
}
}
makeImageMetadata(metadata) {
return {
format: metadata.format,

@ -222,15 +222,20 @@ class OtpAuthService extends SiteService {
return true;
}
async destroyOtpSession (req, serviceName) {
delete req.session.otp[serviceName];
await this.saveSession(req);
}
async removeForUser (user, serviceName) {
return await OtpAccount.findOneAndDelete({ user: user, service: serviceName });
}
async getOTPAccount (user, serviceName) {
const otpAccount = await OtpAccount.findOne({ user: user._id, service: serviceName })
async getBackupTokens (user, serviceName) {
const tokens = await OtpAccount.findOne({ user: user._id, service: serviceName })
.select('+backupTokens')
.lean();
return otpAccount;
return tokens.backupTokens;
}
}

@ -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

@ -13,6 +13,18 @@ ul(uk-nav).uk-nav-default
i.fas.fa-cog
span.uk-margin-small-left Settings
li(class={ 'uk-active': (adminView === 'image-settings') })
a(href="/admin/settings/images")
span.nav-item-icon
i.fas.fa-image
span.uk-margin-small-left Image Settings
li(class={ 'uk-active': (adminView === 'otp') })
a(href="/admin/otp")
span.nav-item-icon
i.fas.fa-cog
span.uk-margin-small-left Otp Settings
li(class={ 'uk-active': (adminView === 'otp') })
a(href="/admin/otp")
span.nav-item-icon

@ -0,0 +1,28 @@
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-base.png`,
siteIcon,
{ aspectRatio: 1 },
)

@ -40,7 +40,7 @@ include section-title
function getUserProfileUrl (user) {
if (user.core) {
return `/user/core/${user._id}`;
return `/user/core/${user.username}`;
}
return `/user/${user.username}`;
}
@ -60,6 +60,6 @@ mixin renderBackButton (options)
mixin renderUserLink (user)
if user.coreUserId
a(href=`/user/core/${user._id}`)= `${user.username}@${user.core.meta.domainKey}`
a(href=`/user/core/${user.username}`)= `${user.username}@${user.core.meta.domainKey}`
else
a(href=`/user/${user.username}`)= user.displayName || user.username

@ -67,7 +67,7 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top
li.uk-nav-heading.uk-text-center= user.displayName || user.username
li.uk-nav-divider
li
a(href= user.core ? `/user/core/${user._id}` : `/user/${user.username}`)
a(href= user.core ? `/user/core/${user.username}` : `/user/${user.username}`)
span.nav-item-icon
i.fas.fa-user
span Profile

@ -60,7 +60,7 @@ mixin renderMenuItem (iconClass, label)
.uk-width-expand Chat
li(class={ "uk-active": (currentView === 'user-settings') })
a(href=`/user/${user._id}`).uk-display-block
a(href=`/user/${user.username}`).uk-display-block
div(uk-grid).uk-grid-collapse
.uk-width-auto
.app-menu-icon

@ -1,6 +1,8 @@
extends main
block content-container
block content-header
section.uk-section.uk-section-default.uk-section-small
.uk-container
div(uk-grid)#dtp-content-grid

@ -1,9 +1,9 @@
mixin renderNewsroomFeedEntryListItem (entry)
.uk-text-bold
a(href= entry.link, target="_blank").uk-link-reset= entry.title
.uk-text-small
div(uk-grid).uk-grid-small
.uk-width-expand
a(href= entry.link, target="_blank").dtp-link
div= entry.title
.uk-article-meta
div(uk-grid).uk-grid-small.uk-grid-divider
.uk-width-auto.uk-text-truncate
a(href= entry.feed.link, target="_blank").uk-link-reset= entry.feed.title
.uk-width-auto
div= moment(entry.published).fromNow()

@ -4,10 +4,10 @@ block content
section.uk-section.uk-section-default
.uk-container
if Array.isArray(hosts) && (hosts.length > 0)
.uk-card.uk-card-default
.uk-card-header
h1.uk-card-title Select Community
.uk-card-body
div(uk-grid).uk-grid-small
each core in connectedCores
@ -26,3 +26,11 @@ block content
div(uk-grid).uk-grid-small
.uk-width-expand
+renderBackButton()
else
.uk-card.uk-card-default
.uk-card-header
h1.uk-card-title There are no communities connected to this site
.uk-card-footer
div(uk-grid).uk-grid-small
.uk-width-expand
+renderBackButton()

@ -508,27 +508,41 @@ export default class DtpSiteAdminHostStatsApp extends DtpApp {
return false;
}
async generateOTPTokens (event) {
async submitImageForm (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const user = target.getAttribute('data-user');
const formElement = event.currentTarget || event.target;
const form = new FormData(formElement);
this.cropper.getCroppedCanvas().toBlob(async (imageData) => {
try {
await UIkit.modal.confirm(`Are you sure you want to regenerate your OTP tokens?`);
form.append('imageFile', imageData, 'icon.png');
this.log.info('submitImageForm', 'updating site image', { event, action: formElement.action });
const response = await fetch(formElement.action, {
method: formElement.method,
body: form,
});
if (!response.ok) {
let json;
try {
json = await response.json();
} catch (error) {
return false;
throw new Error('Server error');
}
throw new Error(json.message || 'Server error');
}
try {
const response = await fetch(`/admin/otp/generate/${user}`, { method: 'POST' });
await this.processResponse(response);
window.location.reload();
} catch (error) {
UIkit.modal.alert(`Failed to generate OTP tokens: ${error.message}`);
UIkit.modal.alert(`Failed to update site image: ${error.message}`);
}
});
return false;
return;
}
}

@ -50,6 +50,13 @@ body {
}
}
a.dtp-link {
color: inherit;
&:hover {
color: @global-link-hover-color;
}
}
.dtp-site-footer {
.uk-subnav {
a.dtp-social-link {

@ -36,13 +36,15 @@ module.config = {
venueService.channelMiddleware(),
gabTvService.channelMiddleware()
);
const { feed: feedService } = dtp.services;
app.use(feedService.middleware({ maxEntryCount: 5 }));
},
};
module.log = new SiteLog(module, module.config.component);
module.shutdown = async ( ) => {
await SitePlatform.shutdown();
};
(async ( ) => {

@ -106,6 +106,13 @@ class SiteCommon extends EventEmitter2 {
});
}
isSystemRoute (pathname) {
return pathname.startsWith('/auth') ||
pathname.startsWith('/image') ||
pathname.startsWith('/manifest')
;
}
isValidString (text) {
return text && (typeof text === 'string') && (text.length > 0);
}

Loading…
Cancel
Save