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.
- Create APIs and endpoints
- Add a comment function to the news detail screen
Prerequisite
Nuxt.js project with Kuroco
This tutorial requires the followings:
- A Nuxt.js project with Kuroco
- Some topics that can be displayed on the front-end
- The
profile
endpoint should be valid. If you haven't built a Nuxt.js project yet, please refer to Tutorial -> Creating a content list page with Kuroco and Nuxt.js. Also, please refer to Tutorial -> Building a login page using Kuroco and Nuxt.js forprofile
endpoint etc. This time, we assume login control by Cookie.
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.
Enter the followings in the API creating modal and click [Add].
item | settings |
---|---|
Title | Comment Test |
Version | 1.0 |
Description | API for testing comment function |
CORS settings
Next, we set up CORS.
Click [Operation CORS].
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].
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].
Click [Add].
Make settings as below and click [Add].
Make a note of the activity ID you created as you will use it later.
Create endpoint
We create following endpoints:
newsdetail
-> to fetch news detailcomments
-> to fetch commentscomment
-> to add commentcomment_delete
-> to delete comments
Click [Add new endpoint] on Comment Test
API screen.
Create newsdetail
endpoint
Create newsdetail
endpoint as follows:
item | settings |
---|---|
Path | newsdetail |
Category | Content |
Model | Topics v1 |
Operation | details |
API request restriction | None |
topics_group_id | Topics 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:
item | settings |
---|---|
Path | comments |
Category | Activity |
Model | Comment v1 |
Operation | list |
API request restriction | None |
id | Activity ID(35) |
module_type | topics |
new_order_flg | check |
After configuring the above settings, click [Add] to complete comments
endpoint settings.
Create comment
endpoint
Create comment
endpoint as follows:
item | settings |
---|---|
Path | comment |
Category | Activity |
Model | Comment v1 |
Operation | insert |
API request restriction | None |
id | Activity ID(35) |
After configuring the above settings, click [Add] to complete comment
endpoint settings.
Create comment_delete
endpoint
Create comment_delete
endpoint as follows:
item | settings |
---|---|
Path | comment_delete |
Category | Activity |
Model | Comment v1 |
Operation | delete |
API request restriction | None |
id | Activity ID(35) |
After configuring the above settings, click [Add] to complete comment_delete
endpoint settings.
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.
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>
Replace the endpoints /rcms-api/13
and newsdetail/12
with your own one.
Confirm that you can add a comment as below.
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.
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].
Click the title of the activity you created before.
Change the "API request restriction" and "Posting restriction" of "All members who have not logged in" to Set limit
and disabled
and click [Update].
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>
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.
This is all for this tutorial.
Support
If you have any other questions, please contact us or check out Our Slack Community.