Browse Source

many feature completions and bugs fixed

develop
Rob Colbert 2 years ago
parent
commit
3b074e3a05
  1. 38
      app/controllers/author.js
  2. 13
      app/controllers/post.js
  3. 3
      app/services/page.js
  4. 59
      app/services/post.js
  5. 7
      app/views/admin/layouts/main.pug
  6. 15
      app/views/admin/newsroom/editor.pug
  7. 2
      app/views/admin/newsroom/index.pug
  8. 88
      app/views/author/index.pug
  9. 58
      app/views/author/post/index.pug
  10. 13
      app/views/components/page-footer.pug
  11. 8
      app/views/components/pagination-bar.pug
  12. 29
      app/views/post/components/draft-list.pug
  13. 32
      app/views/post/components/list.pug
  14. 17
      app/views/post/components/summary.pug
  15. 11
      app/views/post/view.pug
  16. 75
      client/js/site-admin-app.js
  17. 50
      client/js/site-app.js
  18. 3
      client/less/site/content.less
  19. 16
      config/limiter.js

38
app/controllers/author.js

@ -17,9 +17,8 @@ class AuthorController extends SiteController {
async start ( ) {
const { dtp } = this;
const {
limiter: limiterService,
} = dtp.services;
const { limiter: limiterService } = dtp.services;
const { author: authorLimiter } = limiterService.config;
// const upload = multer({ dest: `/tmp/dtp-sites/${this.dtp.config.site.domainKey}`});
@ -36,18 +35,43 @@ class AuthorController extends SiteController {
},
);
router.get('/post',
limiterService.createMiddleware(authorLimiter.getPostIndex),
this.getPostHome.bind(this),
);
router.get('/',
limiterService.createMiddleware(limiterService.config.post.getIndex),
limiterService.createMiddleware(authorLimiter.getIndex),
this.getAuthorHome.bind(this),
);
}
async getPostHome (req, res, next) {
const { post: postService } = this.dtp.services;
try {
res.locals.drafts = await postService.getForAuthor(req.user, ['draft'], { skip: 0, cpp: 5 });
res.locals.archive = await postService.getForAuthor(req.user, ['archived'], { skip: 0, cpp: 5 });
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.published = await postService.getForAuthor(req.user, ['published'], res.locals.pagination);
this.log.debug('published posts for author', { count: res.locals.published.totalPostCount });
res.render('author/post/index');
} catch (error) {
this.log.error('failed to render Author dashboard', { error });
return next(error);
}
}
async getAuthorHome (req, res, next) {
const { /*comment: commentService,*/ post: postService } = this.dtp.services;
try {
res.locals.drafts = await postService.getForAuthor(req.user, 'draft');
res.locals.posts = await postService.getForAuthor(req.user, 'published', 5);
res.locals.comments = await postService.getCommentsForAuthor(req.user);
res.locals.drafts = await postService.getForAuthor(req.user, ['draft'], { skip: 0, cpp: 5 });
res.locals.published = await postService.getForAuthor(req.user, ['published'], { skip: 0, cpp: 5 });
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.authorComments = await postService.getCommentsForAuthor(req.user, res.locals.pagination);
res.render('author/index');
} catch (error) {
this.log.error('failed to render Author dashboard', { error });

13
app/controllers/post.js

@ -313,22 +313,25 @@ class PostController extends SiteController {
}
}
async deletePost (req, res, next) {
async deletePost (req, res) {
const { post: postService } = this.dtp.services;
try {
if (!req.user._id.equals(res.locals.post.author._id)) {
throw new SiteError(403, 'This is not your post');
}
const displayList = this.createDisplayList('add-recipient');
await postService.deletePost(res.locals.post);
const displayList = this.createDisplayList('add-recipient');
displayList.reload();
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update post', { newletterId: res.locals.post._id, error });
return next(error);
this.log.error('failed to remove post', { newletterId: res.locals.post._id, error });
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
});
}
}
}

3
app/services/page.js

@ -23,7 +23,7 @@ class PageService extends SiteService {
async menuMiddleware (req, res, next) {
try {
const pages = await Page
.find({ parent: { $exists: false } })
.find({ status: 'published', parent: { $exists: false } })
.select('slug menu')
.lean();
@ -41,6 +41,7 @@ class PageService extends SiteService {
.sort((a, b) => {
return a.order < b.order;
});
return next();
} catch (error) {
this.log.error('failed to build page menu', { error });

59
app/services/post.js

@ -230,25 +230,34 @@ class PostService extends SiteService {
return posts;
}
async getForAuthor (author, status, maxCount) {
let q = Post
.find({
authorType: author.type,
author: author._id,
status,
})
.sort({ created: -1 })
.populate(this.populatePost);
if (maxCount) {
q = q.limit(maxCount);
async getForAuthor (author, status, pagination) {
if (!Array.isArray(status)) {
status = [status];
}
const posts = await q.lean();
pagination = Object.assign({ skip: 0, cpp: 5 }, pagination);
const search = {
authorType: author.type,
author: author._id,
status: { $in: status },
};
const posts = await Post
.find(search)
.sort({ created: -1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populatePost)
.lean();
posts.forEach((post) => {
post.author.type = post.authorType;
});
return posts;
const totalPostCount = await Post.countDocuments(search);
return { posts, totalPostCount };
}
async getCommentsForAuthor (author, pagination) {
@ -257,18 +266,20 @@ class PostService extends SiteService {
const NOW = new Date();
const START_DATE = moment(NOW).subtract(3, 'days').toDate();
const posts = await this.getForAuthor(author, 'published', 5);
if (!posts || (posts.length === 0)) {
const published = await this.getForAuthor(author, 'published', 5);
if (!published || (published.length === 0)) {
return [ ];
}
const postIds = posts.map((post) => post._id);
const postIds = published.posts.map((post) => post._id);
const search = { // index: 'comment_replies'
created: { $gt: START_DATE },
status: 'published',
resource: { $in: postIds },
};
let q = Comment
.find({ // index: 'comment_replies'
created: { $gt: START_DATE },
status: 'published',
resource: { $in: postIds },
})
.find(search)
.sort({ created: -1 });
if (pagination) {
@ -280,8 +291,8 @@ class PostService extends SiteService {
const comments = await q
.populate(commentService.populateCommentWithResource)
.lean();
return comments;
const totalCommentCount = await Comment.countDocuments(search);
return { comments, totalCommentCount };
}
async getById (postId) {

7
app/views/admin/layouts/main.pug

@ -4,14 +4,9 @@ block vendorcss
block content-container
block page-header
section.uk-section.uk-section-header.uk-section-xsmall
.uk-container
h1.uk-text-center.uk-margin-remove #{site.name} Admin
block admin-layout
section.uk-section.uk-section-default.uk-section-small
section.uk-section.uk-section-default.uk-section-xsmall
.uk-container.uk-container-expand
div(uk-grid)
div(class="uk-width-1-1 uk-flex-last uk-width-auto@m uk-flex-first@m")

15
app/views/admin/newsroom/editor.pug

@ -33,7 +33,14 @@ block content
textarea(id="description", name="description", rows="4", placeholder="Enter feed description").uk-textarea.uk-resize-vertical= feed ? feed.description : undefined
.uk-card-footer
button(type="submit").uk-button.uk-button-primary.uk-border-rounded= feed ? 'Update Feed' : 'Add Feed'
if feed
pre= JSON.stringify(feed, null, 2)
div(uk-grid).uk-flex-right.uk-flex-middle
if feed
.uk-width-auto
button(
type="button",
data-feed-id= feed._id,
data-feed-title= feed.title,
onclick="return dtp.adminApp.removeNewsroomFeed(event);",
).uk-button.uk-button-danger.uk-border-rounded Remove Feed
.uk-width-auto
button(type="submit").uk-button.uk-button-primary.uk-border-rounded= feed ? 'Update Feed' : 'Add Feed'

2
app/views/admin/newsroom/index.pug

@ -5,7 +5,7 @@ block content
.uk-width-expand
h1 Newsroom Feeds
.uk-width-auto
a(href='/admin/newsroom/create').uk-button.uk-button-primary #[i.fas.fa-plus]#[span.uk-margin-small-left Add Feed]
a(href='/admin/newsroom/create').uk-button.uk-button-primary.uk-border-rounded #[i.fas.fa-plus]#[span.uk-margin-small-left Add Feed]
if Array.isArray(newsroom.feeds) && (newsroom.feeds.length > 0)
ul.uk-list.uk-list-divider

88
app/views/author/index.pug

@ -1,25 +1,13 @@
extends ../layouts/main
block content
include ../comment/components/comment
include ../components/pagination-bar
mixin renderPostSummaryFull (post)
div(uk-grid).uk-grid-small
if post.image
.uk-width-auto
img(src= `/image/${post.image}`).uk-width-small
.uk-width-expand
.uk-text-large.uk-text-bold(style="line-height: 1em;")= post.title
.uk-text-small.uk-text-muted
div= post.summary
div= moment(post.created).fromNow()
include ../post/components/draft-list
include ../post/components/list
include ../post/components/summary
mixin renderPostSummary (post)
div(uk-grid).uk-grid-small.uk-flex-middle
div(class="uk-width-1-1 uk-width-expand")
.uk-text-large.uk-text-bold(style="line-height: 1em;")= post.title
div(class="uk-width-1-1 uk-width-auto")
div= moment(post.created).fromNow()
include ../comment/components/comment
section.uk-section.uk-section-default.uk-section-xsmall
.uk-container.uk-container-expand
@ -30,61 +18,29 @@ block content
a(href= "/post/compose").uk-button.uk-button-primary.uk-border-rounded
span
i.fas.fa-plus
span.uk-margin-small-left Create Post
span.uk-margin-small-left.uk-text-bold Create Post
div(uk-grid)
div(class="uk-width-1-1 uk-width-2-3@m")
+renderSectionTitle('Recent Comments', { url: '/author/comments', title: 'See All', label: 'SEE ALL' })
.uk-tile.uk-tile-default.uk-tile-small
ul#post-comment-list.uk-list.uk-list-divider.uk-list-large
each comment in comments
li
.uk-margin
a(href=`/post/${comment.resource.slug}`).uk-display-block.uk-link-reset
+renderPostSummary(comment.resource)
+renderComment(comment, { })
.content-block
.uk-margin
ul#post-comment-list.uk-list.uk-list-divider.uk-list-large.uk-margin
each comment in authorComments.comments
li
.uk-margin
a(href=`/post/${comment.resource.slug}`).uk-display-block.uk-link-reset
+renderPostSummary(comment.resource)
+renderComment(comment, { })
+renderPaginationBar('/author', published.totalPostCount)
div(class="uk-width-1-1 uk-width-1-3@m")
if Array.isArray(drafts) && (drafts.length > 0)
.uk-margin
+renderSectionTitle('Drafts')
ul.uk-list.uk-list-divider
each draft in drafts
li
div(uk-grid).uk-grid-small.uk-flex-middle
.uk-width-expand
a(href=`/post/${draft._id}/edit`, title="Edit draft")= draft.title
.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()
.uk-width-auto
button(
type="button",
title="Delete draft",
data-draft-id= draft._id,
data-draft-title= draft.title,
onclick="return dtp.app.deleteDraft(event);",
).uk-button.uk-button-danger.uk-button-small
span
i.fas.fa-trash
+renderPostDraftList(drafts.posts)
if Array.isArray(posts) && (posts.length > 0)
+renderSectionTitle('Posts')
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()
.uk-width-auto
a(href=`/post/${post._id}/edit`).uk-display-block
+renderButtonIcon('fa-pen', 'edit')
.uk-width-auto
span= formatCount(post.stats.totalVisitCount)
span.uk-margin-small-left
i.fas.fa-eye
.uk-margin
+renderSectionTitle('Recent Posts', { title: 'View All', label: 'View All', url: '/author/post' })
+renderPostList(published.posts)

58
app/views/author/post/index.pug

@ -0,0 +1,58 @@
extends ../../layouts/main
block content
include ../../post/components/draft-list
include ../../post/components/list
include ../../post/components/summary
include ../../components/pagination-bar
section.uk-section.uk-section-default.uk-section-small
.uk-container.uk-container-expand
h1 Post 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)

13
app/views/components/page-footer.pug

@ -8,6 +8,13 @@ section.uk-section.uk-section-muted.uk-section-xsmall.dtp-site-footer
.uk-container.uk-text-small.uk-text-center
.uk-margin
ul.uk-subnav.uk-flex-center
if site.shingUrl
li
a(href= site.shingUrl, target="_blank").dtp-social-link
span
img(src="/img/social-icons/shing.png", style="width: auto; height: 1em;")
span.uk-margin-small-left Shing
if site.gabUrl
li
a(href= site.gabUrl, target="_blank").dtp-social-link
@ -70,12 +77,6 @@ section.uk-section.uk-section-muted.uk-section-xsmall.dtp-site-footer
span
img(src="/img/social-icons/spreaker.svg", style="width: auto; height: 1em;")
span.uk-margin-small-left Spreaker
if site.shingUrl
li
a(href= site.shingUrl, target="_blank").dtp-social-link
span
img(src="/img/social-icons/shing.png", style="width: auto; height: 1em;")
span.uk-margin-small-left Shing
if site.discordUrl
li
a(href= site.discordUrl, target="_blank").dtp-social-link

8
app/views/components/pagination-bar.pug

@ -14,14 +14,16 @@ mixin renderPaginationBar (baseUrl, totalItemCount, urlParameters = '')
}
ul(aria-label="Page navigation").uk-pagination.uk-flex-center
li(class= pagination.p === 1 ? 'uk-disabled' : undefined)
li(class= (pagination.p === 1) ? 'uk-disabled' : undefined)
a(href=`${baseUrl}?p=${pagination.p - 1}${urlParameters}`)
span(uk-pagination-previous).uk-margin-small-right
span prev
while startPage <= endPage
li(class= startPage === pagination.p ? 'active' : undefined)
li(class= (startPage === pagination.p) ? 'active' : undefined)
a(href=`${baseUrl}?p=${startPage}${urlParameters}`)= startPage++
li(class= pagination.p === lastPage ? 'disabled' : undefined)
li(data-last-page= lastPage, class= (pagination.p === lastPage) ? 'uk-disabled' : undefined)
a(href=`${baseUrl}?p=${pagination.p + 1}${urlParameters}`)
span next
span(uk-pagination-next).uk-margin-small-left

29
app/views/post/components/draft-list.pug

@ -0,0 +1,29 @@
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()
.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.

32
app/views/post/components/list.pug

@ -0,0 +1,32 @@
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()
.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
div You have authored posts.

17
app/views/post/components/summary.pug

@ -0,0 +1,17 @@
mixin renderPostSummaryFull (post)
div(uk-grid).uk-grid-small
if post.image
.uk-width-auto
img(src= `/image/${post.image}`).uk-width-small
.uk-width-expand
.uk-text-large.uk-text-bold(style="line-height: 1em;")= post.title
.uk-text-small.uk-text-muted
div= post.summary
div= moment(post.created).fromNow()
mixin renderPostSummary (post)
div(uk-grid).uk-grid-small.uk-flex-middle
div(class="uk-width-1-1 uk-width-expand")
.uk-text-large.uk-text-bold(style="line-height: 1em;")= post.title
div(class="uk-width-1-1 uk-width-auto")
div= moment(post.created).fromNow()

11
app/views/post/view.pug

@ -35,8 +35,17 @@ block content
if post.author._id.equals(user._id)
.uk-width-auto
a(href=`/post/${post._id}/edit`)
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')
.uk-width-auto
+renderButtonIcon('fa-eye', displayIntegerValue(post.stats.totalViewCount))
.uk-width-auto

75
client/js/site-admin-app.js

@ -367,6 +367,81 @@ export default class DtpSiteAdminHostStatsApp extends DtpApp {
return false;
}
async removeNewsroomFeed (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const feedId = target.getAttribute('data-feed-id');
const feedTitle = target.getAttribute('data-feed-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove feed "${feedTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/admin/newsroom/${feedId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove feed: ${error.message}`);
}
return false;
}
async deletePost (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const postId = target.getAttribute('data-post-id');
const postTitle = target.getAttribute('data-post-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove post "${postTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/admin/post/${postId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove post: ${error.message}`);
}
return false;
}
async deletePage (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const pageId = target.getAttribute('data-page-id');
const pageTitle = target.getAttribute('data-page-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove page "${pageTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/admin/page/${pageId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove page: ${error.message}`);
}
return false;
}
}
dtp.DtpSiteAdminHostStatsApp = DtpSiteAdminHostStatsApp;

50
client/js/site-app.js

@ -492,6 +492,56 @@ export default class DtpSiteApp extends DtpApp {
UIkit.modal.alert(`Failed to render chart: ${error.message}`);
}
}
async deletePost (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const postId = target.getAttribute('data-post-id');
const postTitle = target.getAttribute('data-post-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove post "${postTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/post/${postId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove post: ${error.message}`);
}
return false;
}
async deletePage (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const pageId = target.getAttribute('data-page-id');
const pageTitle = target.getAttribute('data-page-title');
try {
await UIkit.modal.confirm(`Are you sure you want to remove page "${pageTitle}"?`);
} catch (error) {
// canceled
return false;
}
try {
const response = await fetch(`/page/${pageId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to remove page: ${error.message}`);
}
return false;
}
}
dtp.DtpSiteApp = DtpSiteApp;

3
client/less/site/content.less

@ -1,5 +1,6 @@
.content-block {
padding: @grid-small-gutter-horizontal;
display: block;
padding: @grid-small-gutter-vertical @grid-small-gutter-horizontal;
background-color: @content-background-color;
:last-child {

16
config/limiter.js

@ -51,6 +51,22 @@ module.exports = {
},
},
/*
* AuthorController
*/
author: {
getPostIndex: {
total: 20,
expire: ONE_MINUTE,
message: 'You are sending room invite actions too quickly',
},
getIndex: {
total: 20,
expire: ONE_MINUTE,
message: 'You are sending room invite actions too quickly',
},
},
/*
* ChatController
*/

Loading…
Cancel
Save