Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
# mini-wp
# Mini - WP

**Users Endpoint**

| METHOD | ENDPOINT | HEADERS | BODY | DESCRIPTION | SUCCESS | ERROR
|------|---------|---------| ---------| --------- | -- | -- |
| POST | /register | | name(string), email(string), password(string), picture(file) | Create user | return new User Object | return error
| POST | /login | | email (string), password (string) | Login user | return Jwt Token | return error
| POST | /googleLogin | | token | Login (google) | return Jwt Token | return error
| POST | /verifyToken | | token (jwt) | verify jwt token | return User | return error

**Articles Endpoints**

| METHOD | ENDPOINT | HEADERS | BODY | DESCRIPTION | SUCCESS | ERROR
|--------|----------|---------|------|------------| -- | -- |
| GET | /articles | token | | Get All Articles | return All Users Articles | return error
| GET | /articles/myarticles | token | | Get All LoggedIn User Articles | return All LoggedIn User Articles | return error
| GET | /articles/:articleId | token | | Get One Article | return One Article | return error
| POST | /articles | token | title(string), content(string), author(string), featuredImage(file), tags(array of string) | Create new article | return New Article Object | return error
| PUT | /articles/:articleId | token | title(string), content(string), author(string), featuredImage(file), tags(array of string) | Update An Article | return Updated Article Object | return error
| DELETE | /articles/:articleId | token | | Delete Article | return Deleted Article Object | return error

**Tags Endpoints**

| METHOD | ENDPOINT | HEADERS | BODY | DESCRIPTION | SUCCESS | ERROR
|--------|----------|---------|------|------------| -- | -- |
| GET | /tags | token | | Get All Tags | return All tags | return error
| POST | /tags | token | text(string) | Create new tag | return New Tag Object | return error

**Comments Endpoints**

| METHOD | ENDPOINT | HEADERS | BODY | DESCRIPTION | SUCCESS | ERROR
|--------|----------|---------|------|------------| -- | -- |
| GET | /comments/:articleId | token | | Get All article comments | return All article comments | return error
| POST | /comments/:articleId | token | comment(string) | Create new comment | return New comment Object | return error
| PUT | /comments/:commentId | token | comment(string) | Update a comment | return Updated comment Object | return error
| DELETE | /comments/:commentId | token | | Delete comment | return Deleted comment Object | return error

### Usage


Make new file `.env` With Template:

```
JWT_SECRET=
CLIENT_ID=
CLOUD_BUCKET=
GCLOUD_PROJECT=
KEYFILE_PATH=
```

Run these commands:

```
$ service mongod start
$ npm install
$ npm run dev
```
140 changes: 140 additions & 0 deletions client/components/articleDetail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
Vue.component('article_detail', {
template: `
<div class="container-fluid">
<div class="card mb-3">
<div class="card-header">
<div class="row no-gutters">
<div class="col-sm-1">
<img v-if="article.author" class="img-thumbnail" :src="article.author.picture || '../css/download.png'" style="max-width: 100%; border-radius: 100%">
</div>
<div class="col-sm-11">
<span>
<p v-if="article.author">{{article.author.name}}</p>
</span>
</div>
</div>
</div>

<div class="card-body">
<div class="containers-fluid">
<div class="text-right font-italic">{{new Date(article.createdAt).toLocaleDateString('en-UK', { year: 'numeric', month: 'long', day: 'numeric' })}}</div>
<div class="row">
<div class="col-xs-12 col-md-3">
<img class="img-thumbnail" :src="article.featuredImage || '../css/blank.jpeg'">
</div>
<div class="col-xs-12 col-md-9">
<p class="card-text" mb-5><strong>Title: <span v-html="article.title"></span></strong></p>
<p class="card-text" mb-5><span v-html="article.content"></span></p>
<span id="tags" v-for="tag in article.tags" class="mx-1">
<a href="#" class="badge badge-secondary" @click="searched = tag">{{tag}}</a>
</span>
</div>
</div>
</div>
</div>
</div>
<hr class="my-5">

<h3 v-if="comments.length > 0">Comments</h3>
<div class="card mb-3" v-for="comment in comments">
<div class="card-header">
<div class="row no-gutters">
<div class="col-sm-1">
<img v-if="comment.userId" class="img-thumbnail" :src="comment.userId.picture || '../css/download.png'" style="max-width: 100%; border-radius: 100%">
</div>
<div class="col-sm-11">
<span>
<p v-if="comment.userId">{{comment.userId.name}}</p>
</span>
</div>
</div>
</div>

<div class="card-body">
<div class="containers-fluid">
<div class="text-right font-italic">{{new Date(comment.createdAt).toLocaleDateString('en-UK', { year: 'numeric', month: 'long', day: 'numeric' })}}</div>
<div class="row">
<div class="col-xs-12 col">
<p class="card-text" mb-5><span v-html="comment.comment"></span></p>
<span id="tags" v-for="tag in comment.tags" class="mx-1">
<a href="#" class="badge badge-secondary" @click="searched = tag">{{tag}}</a>
</span>
</div>
</div>
</div>
</div>
</div>
<br>
<h4>Add comment</h4>
<form @submit.prevent="createComment">
<div class="form-group">
<wysiwyg v-model="newComment" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>`,
props: ['article_id'],
data() {
return {
article: {},
comments: [],
newComment: ''
}
},
components: {
wysiwyg: vueWysiwyg.default.component
},
created() {
console.log(this.article_id);

this.getArticle()
this.getAllComments()
},
methods: {
getArticle() {
axios
.get(`${url}/articles/${this.article_id}`, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
this.article = data
})
.catch(err => {
console.log(err);
})
},
getAllComments() {
axios
.get(`${url}/comments/${this.article_id}`, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
this.comments = data.reverse()
})
.catch(err => {
console.log(err);
})
},
createComment() {
console.log(this.newComment);

if (this.newComment) {
axios
.post(`${url}/comments/${this.article_id}`, {comment: this.newComment}, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
console.log(data);
this.comments.push(data)
})
.catch(err => {
console.log(err);

})
} else {
console.log('input comment first');

}
}
},
})
129 changes: 129 additions & 0 deletions client/components/form_article.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000
});

Vue.component('form_article', {
template: `
<div class="container-fluid">
<form @submit.prevent="addArticle">
<div class="form-group">
<label for="exampleInputTitle">Title</label>
<input type="text" class="form-control" id="exampleInputTitle" placeholder="Enter title" v-model="title" required>
</div>
<div class="form-group">
<label for="exampleInputTags">Tags</label>
<vue-tags-input
v-model="tag"
:tags="tags"
:autocomplete-items="filteredItems"
@tags-changed="newTags => tags = newTags"
/>
</div>
<div class="form-group">
<label for="exampleFormControlFile1">Image</label>
<input type="file" class="form-control-file" id="exampleFormControlFile1" @change="fileChange">
</div>
<div class="form-group">
<label for="exampleInputContent">Content</label>
<wysiwyg v-model="content" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>`,
props: ['edit_data'],
data() {
return {
articleId: this.edit_data._id || '',
title: this.edit_data.title || '',
image: '',
content: this.edit_data.content || '',
tags: this.edit_data.tags || [],
tag: '',
autocompleteItems: [],
}
},
components: {
wysiwyg: vueWysiwyg.default.component,
'vue-tags-input': vueTagsInput.default
},
created() {
this.getAllTags()
},
computed: {
filteredItems() {
return this.autocompleteItems.filter(i => {
return i.text.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
}
},
methods: {
getAllTags() {
axios
.get(`${url}/tags`, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
this.autocompleteItems = data
})
.catch(err => {
console.log(err);
})
},
addArticle() {
let newArticle = {
title: this.title,
content: this.content,
tags: this.tags.map(tag => tag.text)
}
let formData = new FormData
formData.append("file", this.image)
formData.append("data", JSON.stringify(newArticle))
if (this.articleId) {
axios
.put(`${url}/articles/${this.articleId}`, formData, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
Toast.fire({
type: 'success',
title: 'Successfully edit article'
})
this.$emit('change_page', 'my_articles')
this.clearForm()
})
.catch(err => {
console.log(err);
})
} else {
axios
.post(`${url}/articles`, formData, {
headers: {token: localStorage.getItem('token')}
})
.then(({data}) => {
Toast.fire({
type: 'success',
title: 'Successfully create new article'
})
this.$emit('change_page', 'my_articles')
this.clearForm()
})
.catch(err => {
console.log(err);
})
}
},
fileChange(e) {
this.image = e.target.files[0]
},
clearForm() {
this.articleId = ''
this.title = ''
this.tags = []
this.image = ''
this.content = ''
}
},
})
Loading