diff --git a/client/components/comments.vue b/client/components/comments.vue index 77b14b77..0974d64c 100644 --- a/client/components/comments.vue +++ b/client/components/comments.vue @@ -131,40 +131,51 @@ export default { methods: { onIntersect (entries, observer, isIntersecting) { if (isIntersecting) { - this.fetch() + this.fetch(true) } }, - async fetch () { + async fetch (silent = false) { this.isLoading = true - const results = await this.$apollo.query({ - query: gql` - query ($locale: String!, $path: String!) { - comments { - list(locale: $locale, path: $path) { - id - render - authorName - createdAt - updatedAt + try { + const results = await this.$apollo.query({ + query: gql` + query ($locale: String!, $path: String!) { + comments { + list(locale: $locale, path: $path) { + id + render + authorName + createdAt + updatedAt + } } } + `, + variables: { + locale: this.$store.get('page/locale'), + path: this.$store.get('page/path') + }, + fetchPolicy: 'network-only' + }) + this.comments = _.get(results, 'data.comments.list', []).map(c => { + const nameParts = c.authorName.toUpperCase().split(' ') + let initials = _.head(nameParts).charAt(0) + if (nameParts.length > 1) { + initials += _.last(nameParts).charAt(0) } - `, - variables: { - locale: this.$store.get('page/locale'), - path: this.$store.get('page/path') - }, - fetchPolicy: 'network-only' - }) - this.comments = _.get(results, 'data.comments.list', []).map(c => { - const nameParts = c.authorName.toUpperCase().split(' ') - let initials = _.head(nameParts).charAt(0) - if (nameParts.length > 1) { - initials += _.last(nameParts).charAt(0) + c.initials = initials + return c + }) + } catch (err) { + console.warn(err) + if (!silent) { + this.$store.commit('showNotification', { + style: 'red', + message: err.message, + icon: 'alert' + }) } - c.initials = initials - return c - }) + } this.isLoading = false this.hasLoadedOnce = true }, @@ -214,59 +225,63 @@ export default { return } - const resp = await this.$apollo.mutate({ - mutation: gql` - mutation ( - $pageId: Int! - $replyTo: Int - $content: String! - $guestName: String - $guestEmail: String - ) { - comments { - create ( - pageId: $pageId - replyTo: $replyTo - content: $content - guestName: $guestName - guestEmail: $guestEmail - ) { - responseResult { - succeeded - errorCode - slug - message + try { + const resp = await this.$apollo.mutate({ + mutation: gql` + mutation ( + $pageId: Int! + $replyTo: Int + $content: String! + $guestName: String + $guestEmail: String + ) { + comments { + create ( + pageId: $pageId + replyTo: $replyTo + content: $content + guestName: $guestName + guestEmail: $guestEmail + ) { + responseResult { + succeeded + errorCode + slug + message + } + id } - id } } + `, + variables: { + pageId: this.pageId, + replyTo: 0, + content: this.newcomment, + guestName: this.guestName, + guestEmail: this.guestEmail } - `, - variables: { - pageId: this.pageId, - replyTo: 0, - content: this.newcomment, - guestName: this.guestName, - guestEmail: this.guestEmail - } - }) - - if (_.get(resp, 'data.comments.create.responseResult.succeeded', false)) { - this.$store.commit('showNotification', { - style: 'success', - message: 'New comment posted successfully.', - icon: 'check' }) - this.newcomment = '' - await this.fetch() - this.$nextTick(() => { - this.$vuetify.goTo(`#comment-post-id-${_.get(resp, 'data.comments.create.id', 0)}`, this.scrollOpts) - }) - } else { + if (_.get(resp, 'data.comments.create.responseResult.succeeded', false)) { + this.$store.commit('showNotification', { + style: 'success', + message: 'New comment posted successfully.', + icon: 'check' + }) + + this.newcomment = '' + await this.fetch() + this.$nextTick(() => { + this.$vuetify.goTo(`#comment-post-id-${_.get(resp, 'data.comments.create.id', 0)}`, this.scrollOpts) + }) + } else { + throw new Error(_.get(resp, 'data.comments.create.responseResult.message', 'An unexpected error occured.')) + } + } catch (err) { this.$store.commit('showNotification', { style: 'red', - message: _.get(resp, 'data.comments.create.responseResult.message', 'An unexpected error occured.'), + message: err.message, icon: 'alert' }) } @@ -286,42 +301,46 @@ export default { this.isBusy = true this.deleteCommentDialogShown = false - const resp = await this.$apollo.mutate({ - mutation: gql` - mutation ( - $id: Int! - ) { - comments { - delete ( - id: $id - ) { - responseResult { - succeeded - errorCode - slug - message + try { + const resp = await this.$apollo.mutate({ + mutation: gql` + mutation ( + $id: Int! + ) { + comments { + delete ( + id: $id + ) { + responseResult { + succeeded + errorCode + slug + message + } } } } + `, + variables: { + id: this.commentToDelete.id } - `, - variables: { - id: this.commentToDelete.id - } - }) - - if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) { - this.$store.commit('showNotification', { - style: 'success', - message: 'Comment was deleted successfully.', - icon: 'check' }) - this.comments = _.reject(this.comments, ['id', this.commentToDelete.id]) - } else { + if (_.get(resp, 'data.comments.delete.responseResult.succeeded', false)) { + this.$store.commit('showNotification', { + style: 'success', + message: 'Comment was deleted successfully.', + icon: 'check' + }) + + this.comments = _.reject(this.comments, ['id', this.commentToDelete.id]) + } else { + throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occured.')) + } + } catch (err) { this.$store.commit('showNotification', { style: 'red', - message: _.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occured.'), + message: err.message, icon: 'alert' }) } @@ -362,6 +381,7 @@ export default { img { max-width: 100%; + border-radius: 5px; } code { diff --git a/server/graph/schemas/comment.graphql b/server/graph/schemas/comment.graphql index 74c9d629..9e35bd86 100644 --- a/server/graph/schemas/comment.graphql +++ b/server/graph/schemas/comment.graphql @@ -42,7 +42,7 @@ type CommentMutation { content: String! guestName: String guestEmail: String - ): CommentCreateResponse @auth(requires: ["write:comments", "manage:system"]) + ): CommentCreateResponse @auth(requires: ["write:comments", "manage:system"]) @rateLimit(limit: 1, duration: 15) update( id: Int! diff --git a/server/modules/comments/default/comment.js b/server/modules/comments/default/comment.js index e56b63c7..cdc7512a 100644 --- a/server/modules/comments/default/comment.js +++ b/server/modules/comments/default/comment.js @@ -4,6 +4,7 @@ const { JSDOM } = require('jsdom') const createDOMPurify = require('dompurify') const _ = require('lodash') const { AkismetClient } = require('akismet-api') +const moment = require('moment') /* global WIKI */ @@ -106,6 +107,14 @@ module.exports = { } } + // -> Check for minimum delay between posts + if (WIKI.data.commentProvider.config.minDelay > 0) { + const lastComment = await WIKI.models.comments.query().select('updatedAt').findOne('authorId', user.id).orderBy('updatedAt', 'desc') + if (lastComment && moment().subtract(WIKI.data.commentProvider.config.minDelay, 'seconds').isBefore(lastComment.updatedAt)) { + throw new Error('Your administrator has set a time limit before you can post another comment. Try again later.') + } + } + // -> Save Comment to DB const cm = await WIKI.models.comments.query().insert(newComment) diff --git a/server/modules/comments/default/definition.yml b/server/modules/comments/default/definition.yml index eac1f69c..4eceee52 100644 --- a/server/modules/comments/default/definition.yml +++ b/server/modules/comments/default/definition.yml @@ -12,11 +12,12 @@ props: title: Akismet API Key default: '' hint: 'Prevent spam by using the Akismet service. Enter your API key here to enable. Leave empty to disable.' + maxWidth: 650 order: 1 minDelay: type: Number title: Post delay default: 30 - hint: 'Minimum delay (in seconds) between comments per IP address.' + hint: 'Minimum delay (in seconds) between comments per account. Note that all guests are considered as a single account.' maxWidth: 400 order: 2