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