diff --git a/client/components/comments.vue b/client/components/comments.vue
index a27cd41f..77b14b77 100644
--- a/client/components/comments.vue
+++ b/client/components/comments.vue
@@ -51,7 +51,7 @@
v-icon(left) mdi-comment
span.text-none Post Comment
v-divider.mt-3(v-if='permissions.write')
- .pa-5.d-flex.align-center.justify-center(v-if='isLoading')
+ .pa-5.d-flex.align-center.justify-center(v-if='isLoading && !hasLoadedOnce')
v-progress-circular(
indeterminate
size='20'
@@ -63,22 +63,38 @@
dense
v-else-if='comments && comments.length > 0'
)
- v-timeline-item(
+ v-timeline-item.comments-post(
color='pink darken-4'
large
v-for='cm of comments'
:key='`comment-` + cm.id'
+ :id='`comment-post-id-` + cm.id'
)
template(v-slot:icon)
- v-avatar
- v-img(src='http://i.pravatar.cc/64')
+ v-avatar(color='blue-grey')
+ //- v-img(src='http://i.pravatar.cc/64')
+ span.white--text.title {{cm.initials}}
v-card.elevation-1
v-card-text
- .caption: strong {{cm.authorName}}
- .overline.grey--text 3 minutes ago
- .mt-3 {{cm.render}}
+ .comments-post-actions(v-if='permissions.manage && !isBusy')
+ v-icon.mr-3(small, @click='editComment(cm)') mdi-pencil
+ v-icon(small, @click='deleteCommentConfirm(cm)') mdi-delete
+ .comments-post-name.caption: strong {{cm.authorName}}
+ .comments-post-date.overline.grey--text {{cm.createdAt | moment('from') }} #[em(v-if='cm.createdAt !== cm.updatedAt') - modified {{cm.updatedAt | moment('from') }}]
+ .comments-post-content.mt-3(v-html='cm.render')
.pt-5.text-center.body-2.blue-grey--text(v-else-if='permissions.write') Be the first to comment.
.text-center.body-2.blue-grey--text(v-else) No comments yet.
+
+ v-dialog(v-model='deleteCommentDialogShown', max-width='500')
+ v-card
+ .dialog-header.is-red Confirm Delete
+ v-card-text.pt-5
+ span Are you sure you want to permanently delete this comment?
+ .caption: strong This action cannot be undone!
+ v-card-chin
+ v-spacer
+ v-btn(text, @click='deleteCommentDialogShown = false') {{$t('common:actions.cancel')}}
+ v-btn(color='red', dark, @click='deleteComment') {{$t('common:actions.delete')}}
diff --git a/client/themes/default/scss/app.scss b/client/themes/default/scss/app.scss
index 2647745a..8a888d01 100644
--- a/client/themes/default/scss/app.scss
+++ b/client/themes/default/scss/app.scss
@@ -823,7 +823,7 @@
border-radius: 7px 7px 0 0;
@at-root .theme--dark & {
- background-color: lighten(mc('grey', '900'), 5%);
+ background-color: lighten(mc('blue-grey', '900'), 5%);
}
}
diff --git a/server/graph/resolvers/comment.js b/server/graph/resolvers/comment.js
index 5e836b4c..f5c1edbe 100644
--- a/server/graph/resolvers/comment.js
+++ b/server/graph/resolvers/comment.js
@@ -40,7 +40,25 @@ module.exports = {
* Fetch list of comments for a page
*/
async list (obj, args, context) {
- return []
+ const page = await WIKI.models.pages.getPage(args)
+ if (page) {
+ if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], {
+ path: page.path,
+ locale: page.localeCode
+ })) {
+ const comments = await WIKI.models.comments.query().where('pageId', page.id)
+ return comments.map(c => ({
+ ...c,
+ authorName: c.name,
+ authorEmail: c.email,
+ authorIP: c.ip
+ }))
+ } else {
+ throw new WIKI.Error.PageViewForbidden()
+ }
+ } else {
+ return []
+ }
}
},
CommentMutation: {
@@ -49,12 +67,31 @@ module.exports = {
*/
async create (obj, args, context) {
try {
- // WIKI.data.commentProvider.create({
- // ...args,
- // user: context.req.user
- // })
+ const cmId = await WIKI.models.comments.postNewComment({
+ ...args,
+ user: context.req.user,
+ ip: context.req.ip
+ })
return {
- responseResult: graphHelper.generateSuccess('New comment posted successfully')
+ responseResult: graphHelper.generateSuccess('New comment posted successfully'),
+ id: cmId
+ }
+ } catch (err) {
+ return graphHelper.generateError(err)
+ }
+ },
+ /**
+ * Delete an Existing Comment
+ */
+ async delete (obj, args, context) {
+ try {
+ await WIKI.models.comments.deleteComment({
+ id: args.id,
+ user: context.req.user,
+ ip: context.req.ip
+ })
+ return {
+ responseResult: graphHelper.generateSuccess('Comment deleted successfully')
}
} catch (err) {
return graphHelper.generateError(err)
diff --git a/server/graph/schemas/comment.graphql b/server/graph/schemas/comment.graphql
index d99c4066..74c9d629 100644
--- a/server/graph/schemas/comment.graphql
+++ b/server/graph/schemas/comment.graphql
@@ -18,7 +18,8 @@ type CommentQuery {
providers: [CommentProvider] @auth(requires: ["manage:system"])
list(
- pageId: Int!
+ locale: String!
+ path: String!
): [CommentPost]! @auth(requires: ["read:comments", "manage:system"])
single(
@@ -41,7 +42,7 @@ type CommentMutation {
content: String!
guestName: String
guestEmail: String
- ): DefaultResponse @auth(requires: ["write:comments", "manage:system"])
+ ): CommentCreateResponse @auth(requires: ["write:comments", "manage:system"])
update(
id: Int!
@@ -85,3 +86,8 @@ type CommentPost {
createdAt: Date!
updatedAt: Date!
}
+
+type CommentCreateResponse {
+ responseResult: ResponseStatus
+ id: Int
+}
diff --git a/server/helpers/error.js b/server/helpers/error.js
index 05b6e244..d7093a2a 100644
--- a/server/helpers/error.js
+++ b/server/helpers/error.js
@@ -97,6 +97,26 @@ module.exports = {
message: 'Too many attempts! Try again later.',
code: 1008
}),
+ CommentGenericError: CustomError('CommentGenericError', {
+ message: 'An unexpected error occured.',
+ code: 8001
+ }),
+ CommentPostForbidden: CustomError('CommentPostForbidden', {
+ message: 'You are not authorized to post a comment on this page.',
+ code: 8002
+ }),
+ CommentContentMissing: CustomError('CommentContentMissing', {
+ message: 'Comment content is missing or too short.',
+ code: 8003
+ }),
+ CommentManageForbidden: CustomError('CommentManageForbidden', {
+ message: 'You are not authorized to manage comments on this page.',
+ code: 8004
+ }),
+ CommentNotFound: CustomError('CommentNotFound', {
+ message: 'This comment does not exist.',
+ code: 8005
+ }),
InputInvalid: CustomError('InputInvalid', {
message: 'Input data is invalid.',
code: 1012
diff --git a/server/models/comments.js b/server/models/comments.js
index f938ba43..228b18af 100644
--- a/server/models/comments.js
+++ b/server/models/comments.js
@@ -1,4 +1,8 @@
const Model = require('objection').Model
+const validate = require('validate.js')
+const _ = require('lodash')
+
+/* global WIKI */
/**
* Comments model
@@ -52,4 +56,102 @@ module.exports = class Comment extends Model {
this.createdAt = new Date().toISOString()
this.updatedAt = new Date().toISOString()
}
+
+ /**
+ * Post New Comment
+ */
+ static async postNewComment ({ pageId, replyTo, content, guestName, guestEmail, user, ip }) {
+ // -> Input validation
+ if (user.id === 2) {
+ const validation = validate({
+ email: _.toLower(guestEmail),
+ name: guestName
+ }, {
+ email: {
+ email: true,
+ length: {
+ maximum: 255
+ }
+ },
+ name: {
+ presence: {
+ allowEmpty: false
+ },
+ length: {
+ minimum: 2,
+ maximum: 255
+ }
+ }
+ }, { format: 'flat' })
+
+ if (validation && validation.length > 0) {
+ throw new WIKI.Error.InputInvalid(validation[0])
+ }
+ }
+
+ content = _.trim(content)
+ if (content.length < 2) {
+ throw new WIKI.Error.CommentContentMissing()
+ }
+
+ // -> Load Page
+ const page = await WIKI.models.pages.getPageFromDb(pageId)
+ if (page) {
+ if (!WIKI.auth.checkAccess(user, ['write:comments'], {
+ path: page.path,
+ locale: page.localeCode
+ })) {
+ throw new WIKI.Error.CommentPostForbidden()
+ }
+ } else {
+ throw new WIKI.Error.PageNotFound()
+ }
+
+ // -> Process by comment provider
+ return WIKI.data.commentProvider.create({
+ page,
+ replyTo,
+ content,
+ user: {
+ ...user,
+ ...(user.id === 2) ? {
+ name: guestName,
+ email: guestEmail
+ } : {},
+ ip
+ }
+ })
+ }
+
+ /**
+ * Delete an Existing Comment
+ */
+ static async deleteComment ({ id, user, ip }) {
+ // -> Load Page
+ const pageId = await WIKI.data.commentProvider.getPageIdFromCommentId(id)
+ if (!pageId) {
+ throw new WIKI.Error.CommentNotFound()
+ }
+ const page = await WIKI.models.pages.getPageFromDb(pageId)
+ if (page) {
+ if (!WIKI.auth.checkAccess(user, ['manage:comments'], {
+ path: page.path,
+ locale: page.localeCode
+ })) {
+ throw new WIKI.Error.CommentManageForbidden()
+ }
+ } else {
+ throw new WIKI.Error.PageNotFound()
+ }
+
+ // -> Process by comment provider
+ await WIKI.data.commentProvider.remove({
+ id,
+ page,
+ user: {
+ ...user,
+ ip
+ }
+ })
+ }
}
diff --git a/server/modules/comments/default/comment.js b/server/modules/comments/default/comment.js
index 3064b178..e56b63c7 100644
--- a/server/modules/comments/default/comment.js
+++ b/server/modules/comments/default/comment.js
@@ -74,7 +74,7 @@ module.exports = {
ip: user.ip
}
- // Check for Spam with Akismet
+ // -> Check for Spam with Akismet
if (akismetClient) {
let userRole = 'user'
if (user.groups.indexOf(1) >= 0) {
@@ -106,16 +106,33 @@ module.exports = {
}
}
- // Save Comment
- await WIKI.models.comments.query().insert(newComment)
+ // -> Save Comment to DB
+ const cm = await WIKI.models.comments.query().insert(newComment)
+
+ // -> Return Comment ID
+ return cm.id
},
async update ({ id, content, user, ip }) {
},
+ /**
+ * Delete an existing comment by ID
+ */
async remove ({ id, user, ip }) {
-
+ return WIKI.models.comments.query().findById(id).delete()
},
- async count ({ pageId }) {
-
+ /**
+ * Get the page ID from a comment ID
+ */
+ async getPageIdFromCommentId (id) {
+ const result = await WIKI.models.comments.query().select('pageId').findById(id)
+ return (result) ? result.pageId : false
+ },
+ /**
+ * Get the total comments count for a page ID
+ */
+ async count (pageId) {
+ const result = await WIKI.models.comments.query().count('* as total').where('pageId', pageId).first()
+ return _.toSafeInteger(result.total)
}
}