-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature-[167190542]: implement article tagging (#41)
- create tag migration, model and seed files - create articletag migration file - enable user tag article - write model, helper and end to end test - enable admin create category - implement get all tag route [Delivers #167190542]
- Loading branch information
Showing
27 changed files
with
713 additions
and
61 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import model from '../database/models'; | ||
import { imageUpload, serverResponse, serverError } from '../helpers'; | ||
import Tags from './Tags'; | ||
|
||
const { Article } = model; | ||
/** | ||
* @export | ||
* @class Articles | ||
*/ | ||
class Articles { | ||
/** | ||
* @name createArticle | ||
* @async | ||
* @static | ||
* @memberof Articles | ||
* @param {Object} req express request object | ||
* @param {Object} res express response object | ||
* @returns {JSON} JSON object with details of new article | ||
*/ | ||
static async createArticle(req, res) { | ||
try { | ||
let image; | ||
const { | ||
file, | ||
body, | ||
user: { id } | ||
} = req; | ||
const { status, articleBody, tags } = body; | ||
|
||
const publishedAt = status === 'draft' || articleBody === undefined ? null : Date.now(); | ||
let createTags; | ||
if (tags) { | ||
createTags = await Tags.create(tags); | ||
const error = Articles.canTag(createTags); | ||
if (error) return serverResponse(res, error.status, error.message); | ||
} | ||
if (file) image = await imageUpload(req); | ||
const myArticle = await Article.create({ | ||
...body, | ||
image, | ||
authorId: id, | ||
publishedAt | ||
}); | ||
|
||
const associateTags = (await Tags.associateArticle(myArticle.id, createTags)) || []; | ||
myArticle.dataValues.tagList = associateTags; | ||
return serverResponse(res, 200, myArticle.dataValues); | ||
} catch (error) { | ||
return serverError(res); | ||
} | ||
} | ||
|
||
/** | ||
* @name canTag | ||
* @async | ||
* @static | ||
* @memberof Articles | ||
* @param {Array} tagArray array of tags | ||
* @returns {Object} response object | ||
*/ | ||
static canTag(tagArray) { | ||
if (tagArray === false) { | ||
return { | ||
status: 400, | ||
message: { | ||
error: 'each tag must be more than a character' | ||
} | ||
}; | ||
} | ||
if (tagArray === null || tagArray.length < 1 || !tagArray[0].id) { | ||
return { | ||
status: 400, | ||
message: { | ||
error: 'tags should be an array of valid strings' | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
export default Articles; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import models from '../database/models'; | ||
import { | ||
serverResponse, | ||
serverError, | ||
removeSpecialCharacters, | ||
formatTag, | ||
removeDuplicateTags | ||
} from '../helpers'; | ||
|
||
const { Tag, Article } = models; | ||
|
||
/** | ||
* @export | ||
* @class Tags | ||
*/ | ||
class Tags { | ||
/** | ||
* @name getAll | ||
* @async | ||
* @static | ||
* @memberof Tags | ||
* @param {Object} req express request object | ||
* @param {Object} res express response object | ||
* @returns {JSON} JSON object with list of tags | ||
*/ | ||
static async getAll(req, res) { | ||
try { | ||
const allTags = await Tag.findAll({ attributes: ['name'] }); | ||
const tags = allTags.map(({ name }) => name); | ||
return serverResponse(res, 200, { tags }); | ||
} catch (error) { | ||
serverError(res); | ||
} | ||
} | ||
|
||
/** | ||
* @name create | ||
* @async | ||
* @static | ||
* @memberof Tags | ||
* @param {String} tagList list of tags | ||
* @returns {Array} array with accurate tag details | ||
*/ | ||
static async create(tagList) { | ||
if (!tagList || tagList.length < 1) return null; | ||
const tagArray = tagList.split(','); | ||
if (tagArray.some(tag => tag.length < 2)) return false; | ||
const plainTags = tagArray | ||
.map(eachTag => formatTag(eachTag)) | ||
.filter(tag => tag !== ''); | ||
const uniquePlainTags = removeDuplicateTags(plainTags); | ||
const allTag = await Tag.findAll(); | ||
const SaveOrGetTagDetails = await Promise.all( | ||
uniquePlainTags.map(async (eachTag) => { | ||
const existingTag = allTag.find( | ||
tag => removeSpecialCharacters(tag.dataValues.name) | ||
=== removeSpecialCharacters(eachTag) | ||
); | ||
if (!existingTag) { | ||
const newTag = await Promise.resolve(Tag.create({ name: eachTag })); | ||
return newTag.dataValues; | ||
} | ||
return existingTag.dataValues; | ||
}) | ||
); | ||
return SaveOrGetTagDetails; | ||
} | ||
|
||
/** | ||
* @name associateArticle | ||
* @async | ||
* @static | ||
* @memberof Tags | ||
* @param {Integer} articleId id of article to associate | ||
* @param {Array} tagArray array of tags | ||
* @returns {null} null | ||
*/ | ||
static async associateArticle(articleId, tagArray) { | ||
if (!articleId || !tagArray) return false; | ||
const article = await Article.findById(articleId); | ||
const arrayOfTagId = tagArray.map(({ id }) => id); | ||
await article.addTags(arrayOfTagId); | ||
return tagArray.map(({ name }) => name); | ||
} | ||
} | ||
|
||
export default Tags; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export default { | ||
up: (queryInterface, Sequelize) => queryInterface.createTable('Tags', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
name: { | ||
allowNull: false, | ||
type: Sequelize.STRING, | ||
unqiue: true | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}), | ||
down: queryInterface => queryInterface.dropTable('Tags') | ||
}; |
37 changes: 37 additions & 0 deletions
37
server/database/migrations/20190813161118-create-article-tag.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => queryInterface.createTable('ArticleTags', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
articleId: { | ||
type: Sequelize.INTEGER, | ||
allowNull: false, | ||
onUpdate: 'CASCADE', | ||
references: { | ||
model: 'Articles', | ||
key: 'id' | ||
} | ||
}, | ||
tagId: { | ||
type: Sequelize.INTEGER, | ||
allowNull: false, | ||
onUpdate: 'CASCADE', | ||
references: { | ||
model: 'Tags', | ||
key: 'id' | ||
} | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}), | ||
down: queryInterface => queryInterface.dropTable('ArticleTags') | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export default (sequelize, DataTypes) => { | ||
const Tag = sequelize.define( | ||
'Tag', | ||
{ | ||
name: { | ||
allowNull: false, | ||
type: DataTypes.STRING, | ||
unique: true, | ||
validate: { | ||
len: { | ||
args: [2], | ||
msg: 'tag name should be greater than one character' | ||
} | ||
} | ||
} | ||
}, | ||
{} | ||
); | ||
|
||
Tag.associate = (models) => { | ||
Tag.belongsToMany(models.Article, { | ||
foreignKey: 'tagId', | ||
otherKey: 'articleId', | ||
through: 'ArticleTags', | ||
as: 'articles', | ||
onUpdate: 'CASCADE', | ||
onDelete: 'CASCADE' | ||
}); | ||
}; | ||
return Tag; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export default { | ||
up: queryInterface => queryInterface.bulkInsert( | ||
'Tags', | ||
[ | ||
{ | ||
name: 'foot-ball', | ||
createdAt: new Date(), | ||
updatedAt: new Date() | ||
}, | ||
{ | ||
name: 'fut-ball', | ||
createdAt: new Date(), | ||
updatedAt: new Date() | ||
} | ||
], | ||
{} | ||
), | ||
|
||
down: queryInterface => queryInterface.bulkDelete('Tags', null, {}) | ||
}; |
Oops, something went wrong.