parent
abade4701d
commit
065e7fbfdd
@ -0,0 +1,109 @@
|
|||||||
|
// admin/announcement.js
|
||||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||||
|
// License: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const { SiteController } = require('../../../lib/site-lib');
|
||||||
|
|
||||||
|
class AnnouncementAdminController extends SiteController {
|
||||||
|
|
||||||
|
constructor (dtp) {
|
||||||
|
super(dtp, module.exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start ( ) {
|
||||||
|
const router = express.Router();
|
||||||
|
router.use(async (req, res, next) => {
|
||||||
|
res.locals.currentView = 'admin';
|
||||||
|
res.locals.adminView = 'announcement';
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.param('announcementId', this.populateAnnouncementId.bind(this));
|
||||||
|
|
||||||
|
router.post('/:announcementId', this.postUpdateAnnouncement.bind(this));
|
||||||
|
router.post('/', this.postCreateAnnouncement.bind(this));
|
||||||
|
|
||||||
|
router.get('/create', this.getAnnouncementEditor.bind(this));
|
||||||
|
router.get('/:announcementId', this.getAnnouncementEditor.bind(this));
|
||||||
|
|
||||||
|
router.get('/', this.getHomeView.bind(this));
|
||||||
|
|
||||||
|
router.delete('/:announcementId', this.deleteAnnouncement.bind(this));
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateAnnouncementId (req, res, next, announcementId) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
res.locals.announcement = await announcementService.getById(announcementId);
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async postUpdateAnnouncement (req, res, next) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
await announcementService.update(res.locals.announcement, req.body);
|
||||||
|
res.redirect('/admin/announcement');
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async postCreateAnnouncement (req, res, next) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
await announcementService.create(req.body);
|
||||||
|
res.redirect('/admin/announcement');
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnnouncementEditor (req, res) {
|
||||||
|
res.render('admin/announcement/editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHomeView (req, res, next) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
res.locals.pagination = this.getPaginationParameters(req, 20);
|
||||||
|
res.locals.announcements = await announcementService.getAnnouncements(res.locals.pagination);
|
||||||
|
res.render('admin/announcement/index');
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAnnouncement (req, res) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
const displayList = this.createDisplayList('delete-announcement');
|
||||||
|
await announcementService.remove(res.locals.announcement);
|
||||||
|
displayList.reloadView();
|
||||||
|
res.status(200).json({ success: true, displayList });
|
||||||
|
} catch (error) {
|
||||||
|
this.log.error('failed to delete announcement', { error });
|
||||||
|
res.status(error.statusCode || 500).json({
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'announcement',
|
||||||
|
slug: 'announcement',
|
||||||
|
create: async (dtp) => {
|
||||||
|
let controller = new AnnouncementAdminController(dtp);
|
||||||
|
return controller;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,66 @@
|
|||||||
|
// announcement.js
|
||||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||||
|
// License: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const { SiteController, SiteError } = require('../../lib/site-lib');
|
||||||
|
|
||||||
|
class AnnouncementController extends SiteController {
|
||||||
|
|
||||||
|
constructor (dtp) {
|
||||||
|
super(dtp, module.exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start ( ) {
|
||||||
|
const router = express.Router();
|
||||||
|
this.dtp.app.use('/announcement', router);
|
||||||
|
|
||||||
|
router.param('announcementId', this.populateAnnouncementId.bind(this));
|
||||||
|
|
||||||
|
router.get('/:announcementId', this.getAnnouncementView.bind(this));
|
||||||
|
router.get('/', this.getHome.bind(this));
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateAnnouncementId (req, res, next, announcementId) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
res.locals.announcement = await announcementService.getById(announcementId);
|
||||||
|
if (!res.locals.announcement) {
|
||||||
|
this.log.error('announcement not found', { announcementId });
|
||||||
|
return next(new SiteError(404, 'Announcement not found'));
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnnouncementView (req, res) {
|
||||||
|
res.render('announcement/view');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHome (req, res, next) {
|
||||||
|
const { announcement: announcementService } = this.dtp.services;
|
||||||
|
try {
|
||||||
|
res.locals.pagination = this.getPaginationParameters(req, 10);
|
||||||
|
res.locals.announcements = await announcementService.getAnnouncements(res.locals.pagination);
|
||||||
|
res.render('announcement/index');
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
slug: 'announcement',
|
||||||
|
name: 'announcement',
|
||||||
|
create: async (dtp) => {
|
||||||
|
let controller = new AnnouncementController(dtp);
|
||||||
|
return controller;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
// announcement.js
|
||||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||||
|
// License: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const AnnouncementSchema = new Schema({
|
||||||
|
created: { type: Date, default: Date.now, required: true, index: -1, expires: '21d' },
|
||||||
|
title: {
|
||||||
|
icon: {
|
||||||
|
class: { type: String, default: 'fa-bullhorn', required: true },
|
||||||
|
color: { type: String, default: '#ffffff', required: true },
|
||||||
|
},
|
||||||
|
content: { type: String, required: true },
|
||||||
|
},
|
||||||
|
content: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Announcement', AnnouncementSchema);
|
@ -0,0 +1,119 @@
|
|||||||
|
// announcement.js
|
||||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||||
|
// All Rights Reserved
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const Announcement = mongoose.model('Announcement');
|
||||||
|
|
||||||
|
const { SiteService } = require('../../lib/site-lib');
|
||||||
|
|
||||||
|
class AnnouncementService extends SiteService {
|
||||||
|
|
||||||
|
constructor (dtp) {
|
||||||
|
super(dtp, module.exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create (announcementDefinition) {
|
||||||
|
const NOW = new Date();
|
||||||
|
const { coreNode: coreNodeService } = this.dtp.services;
|
||||||
|
|
||||||
|
const announcement = new Announcement();
|
||||||
|
announcement.created = NOW;
|
||||||
|
announcement.title = {
|
||||||
|
icon: {
|
||||||
|
class: announcementDefinition.titleIconClass,
|
||||||
|
color: announcementDefinition.titleIconColor,
|
||||||
|
},
|
||||||
|
content: announcementDefinition.titleContent,
|
||||||
|
};
|
||||||
|
announcement.content = announcementDefinition.content.trim();
|
||||||
|
|
||||||
|
await announcement.save();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Broadcast the Announcement to your DTP Constellation.
|
||||||
|
*/
|
||||||
|
const announcementHref = coreNodeService.getLocalUrl(`/announcement/${announcement._id}`);
|
||||||
|
await coreNodeService.sendKaleidoscopeEvent({
|
||||||
|
action: 'announcement',
|
||||||
|
label: announcement.title.content,
|
||||||
|
content: announcement.content,
|
||||||
|
href: announcementHref,
|
||||||
|
});
|
||||||
|
|
||||||
|
return announcement.toObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
async update (announcement, announcementDefinition) {
|
||||||
|
await Announcement.updateOne(
|
||||||
|
{ _id: announcement._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
title: {
|
||||||
|
icon: {
|
||||||
|
class: announcementDefinition.titleIconClass,
|
||||||
|
color: announcementDefinition.titleIconColor,
|
||||||
|
},
|
||||||
|
content: announcementDefinition.titleContent,
|
||||||
|
},
|
||||||
|
content: announcementDefinition.content.trim(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatest (user) {
|
||||||
|
const { user: userService } = this.dtp.services;
|
||||||
|
let announcements = [ ];
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const search = { };
|
||||||
|
if (user.lastAnnouncement) {
|
||||||
|
search.created = { $gt: user.lastAnnouncement };
|
||||||
|
}
|
||||||
|
announcements = await Announcement
|
||||||
|
.find(search)
|
||||||
|
.sort({ created: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.lean();
|
||||||
|
if (announcements && (announcements.length > 0)) {
|
||||||
|
await userService.setLastAnnouncement(user, announcements[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
announcements = await Announcement
|
||||||
|
.find()
|
||||||
|
.sort({ created: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.lean();
|
||||||
|
}
|
||||||
|
|
||||||
|
return announcements;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById (announcementId) {
|
||||||
|
const announcement = await Announcement.findById(announcementId);
|
||||||
|
return announcement;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnnouncements (pagination) {
|
||||||
|
const announcements = await Announcement
|
||||||
|
.find()
|
||||||
|
.sort({ created: -1 })
|
||||||
|
.skip(pagination.skip)
|
||||||
|
.limit(pagination.cpp)
|
||||||
|
.lean();
|
||||||
|
return announcements;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove (announcement) {
|
||||||
|
await Announcement.deleteOne({ _id: announcement._id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
slug: 'announcement',
|
||||||
|
name: 'announcement',
|
||||||
|
create: (dtp) => { return new AnnouncementService(dtp); },
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
extends ../layouts/main
|
||||||
|
block content
|
||||||
|
|
||||||
|
- var formActionUrl = announcement ? `/admin/announcement/${announcement._id}` : '/admin/announcement';
|
||||||
|
|
||||||
|
form(method="POST", action= formActionUrl).uk-form
|
||||||
|
.uk-card.uk-card-default.uk-card-small
|
||||||
|
.uk-card-header
|
||||||
|
h1 Announcement Editor
|
||||||
|
|
||||||
|
.uk-card-body
|
||||||
|
div(uk-grid)
|
||||||
|
.uk-width-1-2
|
||||||
|
.uk-margin
|
||||||
|
label(for="icon-class").uk-form-label Icon Class
|
||||||
|
input(
|
||||||
|
id="icon-class",
|
||||||
|
name="titleIconClass",
|
||||||
|
type="text",
|
||||||
|
placeholder="Enter FontAwesome icon class",
|
||||||
|
value= announcement ? announcement.title.icon.class : 'fa-bullhorn',
|
||||||
|
).uk-input
|
||||||
|
.uk-text-small
|
||||||
|
a(href="https://fontawesome.com/v5/search", target="_blank") Search icons
|
||||||
|
|
||||||
|
.uk-width-1-2
|
||||||
|
.uk-margin
|
||||||
|
label(for="icon-color").uk-form-label Icon Color
|
||||||
|
input(
|
||||||
|
id="icon-color",
|
||||||
|
name="titleIconColor",
|
||||||
|
type="color",
|
||||||
|
value= announcement ? announcement.title.icon.color : '#ff0013',
|
||||||
|
).uk-input
|
||||||
|
|
||||||
|
.uk-margin
|
||||||
|
label(for="title").uk-form-label Title
|
||||||
|
input(
|
||||||
|
id="title",
|
||||||
|
name="titleContent",
|
||||||
|
type="text",
|
||||||
|
placeholder="Enter announcement title", value= announcement ? announcement.title.content : undefined,
|
||||||
|
).uk-input
|
||||||
|
|
||||||
|
.uk-margin
|
||||||
|
label(for="content").uk-form-label Announcement Body
|
||||||
|
textarea(id="content", name="content", rows="4", placeholder="Enter announcement").uk-textarea= announcement ? announcement.content : undefined
|
||||||
|
|
||||||
|
.uk-card-footer
|
||||||
|
button(type="submit").uk-button.dtp-button-primary= announcement ? 'Update Announcement' : 'Create Announcement'
|
@ -0,0 +1,28 @@
|
|||||||
|
extends ../layouts/main
|
||||||
|
block content
|
||||||
|
|
||||||
|
div(uk-grid).uk-grid-small
|
||||||
|
.uk-width-expand
|
||||||
|
h1 Announcements
|
||||||
|
.uk-width-auto
|
||||||
|
a(href="/admin/announcement/create").uk-button.dtp-button-primary
|
||||||
|
span
|
||||||
|
i.fas.fa-plus
|
||||||
|
span.uk-margin-small-left Create
|
||||||
|
|
||||||
|
if Array.isArray(announcements) && (announcements.length > 0)
|
||||||
|
ul.uk-list.uk-list-divider
|
||||||
|
each announcement in announcements
|
||||||
|
li
|
||||||
|
div(uk-grid).uk-grid-small.uk-flex-middle
|
||||||
|
.uk-width-expand
|
||||||
|
a(href=`/admin/announcement/${announcement._id}`)
|
||||||
|
span
|
||||||
|
i(class=`fas ${announcement.title.icon.class}`)
|
||||||
|
span.uk-margin-small-left= announcement.title.content
|
||||||
|
.uk-width-auto
|
||||||
|
button(type="button", data-announcement-id= announcement._id, onclick="return dtp.adminApp.deleteAnnouncement(event);").uk-button.dtp-button-danger
|
||||||
|
span
|
||||||
|
i.fas.fa-trash
|
||||||
|
else
|
||||||
|
div There are no announcements.
|
@ -0,0 +1,12 @@
|
|||||||
|
mixin renderAnnouncement (announcement)
|
||||||
|
.uk-card.uk-card-default.uk-card-small
|
||||||
|
.uk-card-header
|
||||||
|
h1.uk-card-title
|
||||||
|
span
|
||||||
|
i(class=`fas ${announcement.title.icon.class}`, style=`color: ${announcement.title.icon.color}`)
|
||||||
|
span.uk-margin-small-left= announcement.title.content
|
||||||
|
.uk-card-body!= marked.parse(announcement.content, { renderer: marked.Renderer() })
|
||||||
|
.uk-card-footer
|
||||||
|
.uk-text-small.uk-text-muted.uk-flex.uk-flex-between
|
||||||
|
div= moment(announcement.created).format('MMM DD, YYYY')
|
||||||
|
div= moment(announcement.created).format('hh:mm a')
|
@ -0,0 +1,16 @@
|
|||||||
|
extends ../layouts/main
|
||||||
|
block content
|
||||||
|
|
||||||
|
include components/announcement
|
||||||
|
|
||||||
|
section.uk-section.uk-section-default.uk-section-small
|
||||||
|
.uk-container
|
||||||
|
h1 #{site.name} Announcements
|
||||||
|
|
||||||
|
if Array.isArray(announcements) && (announcements.length > 0)
|
||||||
|
ul.uk-list.uk-list-divider
|
||||||
|
each announcement in announcements
|
||||||
|
li
|
||||||
|
+renderAnnouncement(announcement)
|
||||||
|
else
|
||||||
|
div There are no announcements.
|
@ -1,22 +1,22 @@
|
|||||||
extends layouts/main-sidebar
|
extends layouts/main-sidebar
|
||||||
block content
|
block content
|
||||||
|
|
||||||
p
|
img(src="/img/the-bobs.jpg", alt="The Bobs have questions").responsive
|
||||||
img(src="/img/the-bobs.jpg", alt="The Bobs have questions").responsive
|
|
||||||
|
|
||||||
h1 Sample DTP Web Application
|
.content-block
|
||||||
p This application doesn't actually do anything. The Bobs would have questions.
|
h1 Sample DTP Web Application
|
||||||
|
p This application doesn't actually do anything. The Bobs would have questions.
|
||||||
|
|
||||||
if user
|
if user
|
||||||
h2 Current User
|
h2 Current User
|
||||||
pre= JSON.stringify(user, null, 2)
|
pre= JSON.stringify(user, null, 2)
|
||||||
|
|
||||||
if session
|
if session
|
||||||
h2 Session
|
h2 Session
|
||||||
pre= JSON.stringify(session, null, 2)
|
pre= JSON.stringify(session, null, 2)
|
||||||
|
|
||||||
h2 Site Configuration
|
h2 Site Configuration
|
||||||
pre= JSON.stringify(config, null, 2)
|
pre= JSON.stringify(config, null, 2)
|
||||||
|
|
||||||
h2 Package Information
|
h2 Package Information
|
||||||
pre= JSON.stringify(pkg, null, 2)
|
pre= JSON.stringify(pkg, null, 2)
|
@ -0,0 +1,8 @@
|
|||||||
|
.content-block {
|
||||||
|
padding: @global-gutter;
|
||||||
|
background-color: @content-background-color;
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
section.uk-section {
|
||||||
|
|
||||||
|
&.uk-section-default {
|
||||||
|
background-color: @page-background-color;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
.sidebar-widget {
|
||||||
|
display: block;
|
||||||
|
padding: @global-gutter;
|
||||||
|
background-color: @content-background-color;
|
||||||
|
border: solid 1px @content-border-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
Loading…
Reference in new issue