-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
1,931 additions
and
1,269 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,211 @@ | ||
/* eslint-disable require-jsdoc */ | ||
// import isEmpty from 'utils'; | ||
import models from '../models'; | ||
import Util from '../helpers/util'; | ||
|
||
const util = new Util(); | ||
const internalError = () => { | ||
util.setError(500, 'Internal Error'); | ||
return util; | ||
}; | ||
|
||
const notFound = (msg) => { | ||
util.setError(404, `${msg} not found`); | ||
return util; | ||
}; | ||
|
||
const { | ||
Tag, Article, ArticleTag | ||
} = models; | ||
|
||
class TagController { | ||
static async createTag(req, res) { | ||
// create orphan tag | ||
const { name } = req.body; | ||
Tag.findOrCreate({ | ||
where: { name } | ||
}).then((tag) => { | ||
if (tag[1]) { | ||
return res.status(201).json({ msg: `tag ${name} created`, data: tag[0] }); | ||
} | ||
res.status(200).json({ msg: `tag ${name} exists` }); | ||
}).catch(() => { | ||
internalError.send(res); | ||
}); | ||
} | ||
|
||
static async createArticleTag(req, res) { | ||
const article = await Article.findOne({ | ||
where: { id: req.params.articleId }, | ||
}); | ||
const { name } = req.body; | ||
Tag.findOrCreate({ | ||
where: { name }, | ||
raw: true | ||
}).then((tag) => { | ||
if (tag[0]) { | ||
article.addTag(tag[0]); | ||
return res.status(201).json({ msg: `tag ${name} added to article`, data: article }); | ||
} | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static async editTag(req, res) { | ||
// changes the name of a given tag if new name still unique or merges tags | ||
// with all its relationships if new name exists. | ||
// must confirm merger from user | ||
const { name } = req.params; | ||
const newName = req.body.update; | ||
Tag.update({ name: newName }, { | ||
where: { name }, | ||
returning: true, | ||
}).then((updated) => { | ||
if (updated[0] !== 0) { | ||
return res.status(200).json({ | ||
msg: `tag ${name} updated to ${newName}`, data: updated[1] | ||
}); | ||
} | ||
notFound(`tag ${name}`).send(res); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static async editArticleTag(req, res) { | ||
const newTag = await Tag.findOrCreate({ | ||
where: { name: req.body.name } | ||
}); | ||
const oldTag = await Tag.findOne({ | ||
where: { name: req.params.tagName } | ||
}); | ||
ArticleTag.update( | ||
{ tagId: newTag.id, articleId: req.params.articleId }, | ||
{ | ||
where: { | ||
tagId: oldTag.id, articleId: req.params.articleId | ||
}, | ||
returning: true, | ||
raw: true | ||
} | ||
).then((updated) => { | ||
if (updated[0] !== 0) { | ||
res.status(200).json({ msg: 'update successful' }); | ||
} | ||
notFound(oldTag.name).send(res); | ||
}) | ||
.catch(() => internalError()); | ||
} | ||
|
||
static async getTags(req, res) { | ||
// gets and returns an array of objects having tags and the number of | ||
// articles for each tags | ||
Tag.findAll() | ||
.then((tags) => { | ||
if (tags.length === 0) { | ||
notFound('tags').send(res);// not tested | ||
} | ||
// reduce tags list to give article numbers only | ||
res.status(200).json({ tags }); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static getTag(req, res) { | ||
// gets and returns a given tag and the number of articles with this tag | ||
Tag.findOne({ | ||
where: { name: req.params.name }, | ||
include: ['articles'] | ||
}).then((tag) => { | ||
if (tag) { | ||
return res.status(200).json({ | ||
tag: { | ||
id: tag.id, name: tag.name, articleCount: tag.articles.length | ||
} | ||
}); | ||
} | ||
notFound('tag').send(res); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static getTagArticles(req, res) { | ||
// gets an array of all articles with the given tag | ||
Tag.findAll({ | ||
// alternatively use tag.getArticles() | ||
where: { name: req.params.name }, | ||
include: ['articles'], | ||
}).then((articles) => { | ||
if (articles[0].articles.length === 0) { | ||
return notFound(`articles about ${req.params.name}`).send(res); | ||
} | ||
res.status(200).json({ | ||
articles: articles.map(element => element.articles) | ||
}); | ||
// res.status(404).json({ msg: `no articles about ${req.params.name}` }); | ||
notFound(`articles about ${req.params.name}`).send(res); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static async getArticleTags(req, res) { | ||
// returns an array of all tags for a given article | ||
Article.findOne({ | ||
where: { id: req.params.articleId }, | ||
include: ['tags'] | ||
}).then((article) => { | ||
if (!article) { | ||
return res.status(404).json({ error: 'article not found' }); // untested | ||
} | ||
if (article.tags.length === 0) { | ||
return res.status(404).json({ msg: 'article not tagged' }); | ||
} | ||
res.status(200).json({ tags: article.tags.map(tag => tag.name) }); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static async deleteArticleTag(req, res) { | ||
// deletes a given tag of a given article technically the tag and article | ||
// remain undeleted, only the association in the ArticleTags table is | ||
// deleted | ||
ArticleTag.destroy({ | ||
// alternatively, use article.removeTag(tag) | ||
where: { articleId: req.params.articleId, tagId: req.params.tagId } | ||
}).then((deleted) => { | ||
if (deleted) { | ||
return res.status(200).json({ | ||
message: `tag with Id ${req.params.tagId} removed from article` | ||
}); | ||
} | ||
res.status(404).json({ | ||
message: `this article has no tag ${req.params.tagId}` | ||
}); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
|
||
static async deleteTagArticles(req, res) { | ||
// deletes all articles about a particular item/tag | ||
Tag.findOne({ | ||
where: { name: req.params.name }, | ||
include: ['articles'] | ||
}).then((tag) => { | ||
const failed = []; | ||
if (tag.articles.length === 0) { | ||
return res.status(404).json({ | ||
error: `no articles about ${req.params.name}` | ||
}); | ||
} | ||
tag.articles.forEach((article) => { | ||
article.destroy((success) => { | ||
if (success !== {}) { | ||
failed.push(article.id); | ||
} | ||
}); | ||
}); | ||
if (failed.length !== 0) { | ||
return res.status(500).json({ | ||
error: `articles with Ids ${failed.join(', ')} not deleted` | ||
}); | ||
} | ||
res.status(200).json({ | ||
msg: `all articles about ${req.params.name} deleted` | ||
}); | ||
}).catch(() => internalError().send(res)); | ||
} | ||
} | ||
|
||
export default TagController; |
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,13 @@ | ||
import isAlnum from './is.alphanumeric'; | ||
|
||
const validateReq = (req, res, next) => { | ||
if (req.params.articleId && !/^\d+$/.test(req.params.articleId)) { | ||
res.status(400).json({ error: 'articleId must be an integer' }); | ||
} else if (req.params.name && !isAlnum(req.params.name)) { | ||
res.status(400).json({ error: 'tagName invalid' }); | ||
} else if (req.body.name && !isAlnum(req.body.name)) { | ||
res.status(400).json({ error: 'tagName invalid' }); | ||
} else { next(); } | ||
}; | ||
|
||
export default validateReq; |
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,28 @@ | ||
'use strict'; | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => { | ||
return queryInterface.createTable('Tags', { | ||
id: { | ||
allowNull: false, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
type: Sequelize.INTEGER | ||
}, | ||
name: { | ||
type: Sequelize.STRING, | ||
primaryKey: true | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}); | ||
}, | ||
down: (queryInterface, Sequelize) => { | ||
return queryInterface.dropTable('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 @@ | ||
'use strict'; | ||
module.exports = { | ||
up: (queryInterface, Sequelize) => { | ||
return queryInterface.createTable('ArticleTags', { | ||
articleId: { | ||
type: Sequelize.INTEGER | ||
}, | ||
tagId: { | ||
type: Sequelize.INTEGER | ||
}, | ||
createdAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
}, | ||
updatedAt: { | ||
allowNull: false, | ||
type: Sequelize.DATE | ||
} | ||
}); | ||
}, | ||
down: (queryInterface, Sequelize) => { | ||
return 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,12 @@ | ||
'use strict'; | ||
module.exports = (sequelize, DataTypes) => { | ||
const ArticleTag = sequelize.define('ArticleTag', { | ||
articleId: DataTypes.INTEGER, | ||
tagId: DataTypes.INTEGER | ||
}, {}); | ||
ArticleTag.associate = function (models) { | ||
ArticleTag.belongsTo(models.Article, { foreignKey: 'articleId' }) | ||
ArticleTag.belongsTo(models.Tag, { foreignKey: 'tagId' }) | ||
}; | ||
return ArticleTag; | ||
}; |
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,13 @@ | ||
module.exports = (sequelize, DataTypes) => { | ||
const Tag = sequelize.define('Tag', { | ||
name: DataTypes.STRING, | ||
}, {}); | ||
Tag.associate = function (models) { | ||
Tag.belongsToMany(models.Article, { | ||
through: 'ArticleTags', | ||
foreignKey: 'tagId', | ||
as: 'articles' | ||
}) | ||
}; | ||
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
Empty file.
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,16 @@ | ||
import express from 'express'; | ||
import TagController from '../../../controllers/tag'; | ||
import auth from '../../../middlewares/auth'; | ||
import validateReq from '../../../middlewares/validators/validateTags'; | ||
|
||
|
||
const router = express.Router(); | ||
|
||
router.post('/tags', auth, TagController.createTag); | ||
router.get('/tags', TagController.getTags); | ||
router.get('/tags/:name', TagController.getTag); | ||
router.patch('/tags/:name', auth, TagController.editTag); | ||
router.get('/tags/:name/articles', validateReq, TagController.getTagArticles); | ||
router.delete('/tags/:name/articles', auth, TagController.deleteTagArticles); | ||
|
||
export default router; |
Oops, something went wrong.