Skip to main content

Add comments function to content with Kuroco and Nuxt.js

This tutorial explains how to implement the Activity: Comment function in the Nuxt.js project using Kuroco.
This time, as an example, we will implement the process in the following flow.

  1. Create APIs and endpoints
  2. Add a comment function to the news detail screen

Prerequisite

Nuxt.js project with Kuroco

This tutorial requires the followings:

After copying the endpoint of "news details" that has already been created and setting permission for comments by non-logged-in members, create a form for announcement details and related comments.

Preparation

Creating APIs

First, we create a new API to allow operations from unauthorized users. Click [Add] from the API page on the Kuroco admin panel.

Image from Gyazo

Enter the followings in the API creating modal and click [Add].

Image from Gyazo

itemsettings
TitleComment Test
Version1.0
DescriptionAPI for testing comment function

CORS settings

Next, we set up CORS.
Click [Operation CORS].
Image from Gyazo

Click [Add Origin] of CORS_ALLOW_ORIGINS and enter the following:

  • http://localhost:3000

Click [Add Method] CORS_ALLOW_METHODS and enter the following:

  • GET
  • POST
  • OPTIONS

After configuring the above settings, click [Save].
Image from Gyazo

Check the comment function without login

Configure activity settings

In order to allow unauthorized user to comment, we configure the activity settings.
Click [Activity settings].
Image from Gyazo

Click [Add].
Image from Gyazo

Make settings as below and click [Add].
Image from Gyazo

Make a note of the activity ID you created as you will use it later.
Image from Gyazo

Create endpoint

We create following endpoints:

  • newsdetail -> to fetch news detail
  • comments -> to fetch comments
  • comment -> to add comment
  • comment_delete -> to delete comments

Click [Add new endpoint] on Comment Test API screen.
Image from Gyazo

Create newsdetail endpoint

Create newsdetail endpoint as follows:

Image from Gyazo

itemsettings
Pathnewsdetail
CategoryContent
ModelTopics v1
Operationdetails
API request restrictionNone
topics_group_idTopics group ID to display(10)

After configuring the above settings, click [Add] to complete newsdetail endpoint settings.

Create comments endpoint

Create comments endpoint as follows:
Image from Gyazo
Image from Gyazo

itemsettings
Pathcomments
CategoryActivity
ModelComment v1
Operationlist
API request restrictionNone
idActivity ID(35)
module_typetopics
new_order_flgcheck

After configuring the above settings, click [Add] to complete comments endpoint settings.

Create comment endpoint

Create comment endpoint as follows:
Image from Gyazo

itemsettings
Pathcomment
CategoryActivity
ModelComment v1
Operationinsert
API request restrictionNone
idActivity ID(35)

After configuring the above settings, click [Add] to complete comment endpoint settings.

Create comment_delete endpoint

Create comment_delete endpoint as follows:
Image from Gyazo

itemsettings
Pathcomment_delete
CategoryActivity
ModelComment v1
Operationdelete
API request restrictionNone
idActivity ID(35)

After configuring the above settings, click [Add] to complete comment_delete endpoint settings.

caution

In this example, the security of endpoint for adding/deleting comments is set to None, but on the actual site, set a group that allows adding/deleting comments.

Set up front-end

Add news detail page with comment function

This time, we will create a screen referring to the existing news detail screen and add a comment function to the same page.
First, we will check the operation so that even unauthorized users can read/post comments.

It fetches and displays all comments associated with the news when displaying the screen.

There is a user name input field and a comment posting form, and after entering the user name, the added comment is immediately reflected on the screen.
Each comment has a delete button too.

tip

Deletion of comments can only be done by the person who posted the comment, assuming that the password (delkey) is used to post and delete comments, or assuming login.
At this stage, if you click the delete button, an error Code 422 "Cannot delete because the password does not match or you are not the author." will occur.

Add pages/news/test_with_comment.vue and write the code as follows:

<template>
<div>
<h1 class="title">{{ response.details.subject }}</h1>
<div class="post" v-html="response.details.contents"></div>
<p v-if="resultMessage !== null">
{{ resultMessage }}
</p>
<div>
please type your name: <input v-model="userName" type="text" placeholder="your name">
</div>
<div>
<ul v-for="comment in comments" :key="comment.comment_id">
<li>
{{ comment.note }} by {{ comment.name }}
<button type="button" @click="() => deleteComment(comment.comment_id)">
delete
</button>
</li>
</ul>
<form @submit.prevent="submitComment">
<input v-model="inputComment" type="text" placeholder="comment">
<button type="submit" :disabled="inputComment === '' || userName === ''">
submit
</button>
</form>
</div>
</div>
</template>

<script>
async function getAllComments (topics_id) {
const { list } = await this.$axios.$get(
'/rcms-api/13/comments',
{
params: {
module_id: topics_id,
cnt: 999
}
}
)
return list
}

export default {
async asyncData({ $axios, params }) {
try {
const response = await $axios.$get(
'/rcms-api/13/newsdetail/12'
)
return { response, comments: await getAllComments.call({ $axios }, response.details.topics_id) }
} catch (e) {
console.log(e.message)
}
},
data () {
return {
userName: '',
response: null,
comments: [],
inputComment: '',
resultMessage: null,
}
},
methods: {
async submitComment () {
await this.$axios.$post('/rcms-api/13/comment', {
module_id: this.response.details.topics_id,
name: this.userName,
note: this.inputComment
})
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
},
async deleteComment (commentId) {
try{
await this.$axios.$post(`/rcms-api/13/comment_delete/${commentId}`, {})
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
} catch (error) {
this.resultMessage = error.response.data.errors[0].message
}
}
}
}
</script>
caution

Replace the endpoints /rcms-api/13 and newsdetail/12 with your own one.

Confirm that you can add a comment as below.

Image from Gyazo

Make comments deletable (using delkey)

In order to allow unauthorized users to delete comments, we will use delkey to request deletion.

delkey is an arbitrary value that can be given when adding a comment. This time, it will be implemented with the following specifications.

  • Add delkey automatically when adding a comment
  • Store delkey in the browser's local storage
  • Call delkey from browser local storage on delete

Please note that saving to the browser uses local storage, so it cannot be deleted if you change browsers or resume operation on a different device.

Modify pages/news/test_with_comment.vue as below.

@@ -1,85 +1,106 @@
<template>
<div>
<h1 class="title">{{ response.details.subject }}</h1>
<div class="post" v-html="response.details.contents"></div>
<p v-if="resultMessage !== null">
{{ resultMessage }}
</p>
<div>
please type your name: <input v-model="userName" type="text" placeholder="your name">
</div>
<div>
<ul v-for="comment in comments" :key="comment.comment_id">
<li>
{{ comment.note }} by {{ comment.name }}
- <button type="button" @click="() => deleteComment(comment.comment_id)">
+ <button v-if="commentHistory.map(({ id }) => id).includes(comment.comment_id)" type="button" @click="() => deleteComment(comment.comment_id)">
delete
</button>
</li>
</ul>
<form @submit.prevent="submitComment">
<input v-model="inputComment" type="text" placeholder="comment">
<button type="submit" :disabled="inputComment === '' || userName === ''">
submit
</button>
</form>
</div>
</div>
</template>

<script>
async function getAllComments (topics_id) {
const { list } = await this.$axios.$get(
'/rcms-api/13/comments',
{
params: {
module_id: topics_id,
cnt: 999
}
}
)
return list
}

+const COMMENT_HISTORY_KEY = 'CommentHistory'
+
export default {
async asyncData({ $axios, params }) {
try {
const response = await $axios.$get(
'/rcms-api/13/newsdetail/12'
)
return { response, comments: await getAllComments.call({ $axios }, response.details.topics_id) }
} catch (e) {
console.log(e.message)
}
},
data () {
return {
userName: '',
response: null,
comments: [],
inputComment: '',
+ commentHistory: [],
resultMessage: null,
}
},
+ mounted () {
+ this.commentHistory = JSON.parse(localStorage.getItem(COMMENT_HISTORY_KEY)) || []
+ },
methods: {
async submitComment () {
- await this.$axios.$post('/rcms-api/13/comment', {
+ const delkey = `${this.userName}_${Date.now()}`
+ const submitResponse = await this.$axios.$post('/rcms-api/13/comment', {
module_id: this.response.details.topics_id,
name: this.userName,
- note: this.inputComment
+ note: this.inputComment,
+ delkey
})
+ this.addCommentHistory({ id: submitResponse.id, delkey })
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
},
async deleteComment (commentId) {
try{
- await this.$axios.$post(`/rcms-api/13/comment_delete/${commentId}`, {})
+ await this.$axios.$post(`/rcms-api/13/comment_delete/${commentId}`, {
+ delkey: this.commentHistory.find(({ id }) => `${id}` === `${commentId}`).delkey
+ })
+ this.deleteCommentHistory(commentId)
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
} catch (error) {
this.resultMessage = error.response.data.errors[0].message
}
+ },
+ addCommentHistory (payload) {
+ const restored = JSON.parse(localStorage.getItem(COMMENT_HISTORY_KEY)) || []
+ restored.push(payload)
+ localStorage.setItem(COMMENT_HISTORY_KEY, JSON.stringify(restored))
+ this.commentHistory = restored
+ },
+ deleteCommentHistory (commentId) {
+ const restored = JSON.parse(localStorage.getItem(COMMENT_HISTORY_KEY)) || []
+ const filtered = restored.filter(({ id }) => `${id}` !== `${commentId}`)
+ localStorage.setItem(COMMENT_HISTORY_KEY, JSON.stringify(filtered))
+ this.commentHistory = filtered
}
}
}
</script>

Confirm that the delete button is displayed only for comments that have been commented and can be deleted as shown below.
Image from Gyazo

Make comments require login

If you open the POST endpoint for modification/deletion like the comment function without any measures, it becomes vulnerable to DoS attacks (cyberattacks that puncture the DB by posting a large amount of data at the same time).
Kuroco's endpoint has a function to return an error that does not accept posts if there are many comments from the same IP address in a short period of time. But in this tutorial, implementation is done so that it requires users to login for viewing the news detail page, and for displaying, posting and deleting comments.

Update activity settings

Click [Activity settings].
Image from Gyazo

Click the title of the activity you created before.
Image from Gyazo

Change the "API request restriction" and "Posting restriction" of "All members who have not logged in" to Set limit and disabled and click [Update].
Image from Gyazo

Modify front-end

Modify pages/news/test_with_comment.vue as below:

<template>
<div>
<h1 class="title">{{ response.details.subject }}</h1>
<div class="post" v-html="response.details.contents"></div>
<div>
<p v-if="resultMessage !== null">
{{ resultMessage }}
</p>
<ul v-for="comment in comments" :key="comment.comment_id">
<li>
{{ comment.note }} by {{ comment.name }}
<button type="button" @click="() => deleteComment(comment.comment_id)">
delete
</button>
</li>
</ul>
<form @submit.prevent="submitComment">
<input v-model="inputComment" type="text" placeholder="comment">
<button type="submit" :disabled="inputComment === ''">
submit
</button>
</form>
</div>
</div>
</template>

<script>
async function getAllComments (topics_id) {
const { list } = await this.$axios.$get(
'/rcms-api/13/comments',
{
params: {
module_id: topics_id,
cnt: 999
}
}
)
return list
}

import { mapActions } from 'vuex'
export default {
middleware: 'auth',
async asyncData ({ $axios, params }) {
try {
const profile = await $axios.$get('/rcms-api/4/profile')
const response = await $axios.$get(
'/rcms-api/13/newsdetail/12'
)
return { profile, response, comments: await getAllComments.call({ $axios }, response.details.topics_id) }
} catch (e) {
console.log(e.message)
}
},
data () {
return {
userName: '',
response: null,
comments: [],
inputComment: '',
resultMessage: null,
}
},
methods: {
async submitComment () {
await this.$axios.$post('/rcms-api/13/comment', {
module_id: this.response.details.topics_id,
name: `${this.profile.name1} ${this.profile.name2}`,
mail: this.profile.email,
note: this.inputComment
})
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
},
async deleteComment (commentId) {
try{
await this.$axios.$post(`/rcms-api/13/comment_delete/${commentId}`, {})
this.comments = await getAllComments.call(this, this.response.details.topics_id)
this.inputComment = ''
} catch (error) {
this.resultMessage = error.response.data.errors[0].message
}
}
}
}
</script>
caution

Replace the endpoints /rcms-api/13, newsdetail/12, and /rcms-api/4/ with your own one.

Add a link to test_with_comment page in pages/login/index.vue as below:

<template>
<form @submit.prevent="login">
<p v-if="loginStatus !== null" :style="{ color: resultMessageColor }">
{{ resultMessage }}
</p>

<input v-model="email" name="email" type="email" placeholder="email"/>
<input
v-model="password"
name="password"
type="password"
placeholder="password"
/>
<button type="submit">
Login
</button>
<div>
<nuxt-link to="/news">
News list page
</nuxt-link>
<br/>
<nuxt-link to="/news/test_with_comment">
Test with comment
</nuxt-link>
</div>
</form>
</template>

<script>
export default {
data () {
return {
email: '',
password: '',

loginStatus: null,
resultMessage: null
};
},
computed: {
resultMessageColor () {
switch (this.loginStatus) {
case 'success':
return 'green'
case 'failure':
return 'red'
default:
return ''
};
}
},
methods: {
async login () {

try {
const payload = {
email: this.email,
password: this.password
}
await this.$store.dispatch('login', payload)
this.loginStatus = 'success'
this.resultMessage = 'Login successful'
} catch (e) {
this.loginStatus = 'failure'
this.resultMessage = 'Login failed'
};
}
},
};
</script>

Finally, check the operation.
Image from Gyazo

This is all for this tutorial.


Support

If you have any other questions, please contact us or check out Our Slack Community.