Repository & Pull Requests
https://github.com/knacksteem/knacksteem-api/
https://github.com/knacksteem/knacksteem-api/pull/14
https://github.com/knacksteem/knacksteem-api/pull/15
What is KnackSteem?
"Do you have any talent? If yes! then KnackSteem is for you."
"Rewards people with talents, it can be any talent, anything you know how to do best is highly welcome on the platform. "
Source: Discord Channel :D
Changes Made
Endpoint to edit post tags (on backend only)
We use tags to improve our posts search functionality. This point will allow the post owner to edit the tags of this post if a mistake was made. This endpoint will require prior authentification before using it.
Related code:
/**
* Method to update the tags of a post
* @param {Object} req: url params
* @param {Function} res: Express.js response callback
* @param {Function} next: Express.js middleware callback
* @author Jayser Mendez
* @public
*/
exports.updateTags = async (req, res, next) => {
try {
const { permlink, tags } = req.body;
const { username } = res.locals;
const post = await Post.findOne({ permlink });
if (post.author !== username) {
return next({
status: httpStatus.UNAUTHORIZED,
message: 'You cannot edit someone else post',
});
}
await Post.update({ permlink }, { tags });
return res.status(httpStatus.OK).send({
status: httpStatus.OK,
message: 'Post edited correctly',
});
} catch (err) {
console.log(err);
return next({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: 'Opps! Something is wrong in our server. Please report it to the administrator.',
error: err,
});
}
};
Refactor fields in stats endpoints
By default, stats endpoints were not showing if the current user has voted the current loaded post. Indeed, there was an inconsistency in the returned fields in comparison with other endpoints. Also, this was leading to errors in client-side since it is re-using components for the posts. To solve this issue, I replaced the username field (used to get posts by the user) to author and used the username field to confirm is the current user has voted this post. The isVoted field was added to the response to make it identically to the rest.
Related code (coded was truncated to improve visibility):
let isVoted = false;
// Check if there is a user provided in the params.
// If so, determine if this user has voted the post.
if (username) {
isVoted = helper.isVoted(response.active_votes, username);
}
Allow all CORS method in Development mode
Even I was not facing this issue, the frontend developer told me that CORS were denying the PUT method at his end. To solve this issue, we decided to allow all methods in development mode (and we've confirmed that it works properly in production mode).
Related code:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
next();
});
Categories model
Categories were loaded directly from the client side. As an enhancement, I decided to load them dynamically from the server side since we may need to add or remove some categories in the future. to avoid a re-deploy of the client-side, those should be loaded from the backend.
Related code:
const mongoose = require('mongoose');
/**
* Category Schema
* @author Jayser Mendez
* @private
*/
const categorySchema = new mongoose.Schema({
key: {
type: String,
required: true,
trim: true,
lowercase: true,
unique: true,
},
name: {
type: String,
required: true,
trim: true,
},
});
// Declare index in category schema for faster query.
categorySchema.index({
key: 1,
name: 1,
}, { name: 'category_index' });
/**
* @typedef Category
*/
module.exports = mongoose.model('Category', categorySchema);
Endpoint to get all categories
As explained above, categories are now dynamically loaded. Hence, an endpoint is needed to consume them. The endpoint will return a response in the exact way it was in the client side so not too much changes are needed.
Related code:
/**
* Get all the categories in database
* @param {Object} req: url params
* @param {Function} res: Express.js response callback
* @param {Function} next: Express.js middleware callback
* @public
* @author Jayser Mendez
*/
exports.getCategories = async (req, res, next) => {
try {
const categoriesList = await Category.find({}, { __v: 0, _id: 0 });
return res.status(httpStatus.OK).send({
status: httpStatus.OK,
results: categoriesList,
count: categoriesList.length,
});
} catch (err) {
return next({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: 'Opps! Something is wrong in our server. Please report it to the administrator.',
error: err,
});
}
};
Endpoint to add category
Since now categories are dynamic, a supervisor may be able to create categories. So, this endpoint was created with this functionality in mind.
Related code:
/**
* Create a new category in database
* @param {Object} req: url params
* @param {Function} res: Express.js response callback
* @param {Function} next: Express.js middleware callback
* @public
* @author Jayser Mendez
*/
exports.createCategory = async (req, res, next) => {
try {
const { name } = req.body;
// Strip all the empty spaces and special characters and convert to lower case
const key = name.replace(/[^A-Z0-9]+/ig, '').toLowerCase();
const newCategory = await new Category({ key, name });
await Category.create(newCategory);
return res.status(httpStatus.CREATED).send({
status: httpStatus.CREATED,
message: 'Category was correctly created',
});
} catch (err) {
// Return exact error if there is a collision in the key
if (err.message.includes('E11000')) {
return next({
status: httpStatus.NOT_ACCEPTABLE,
message: 'Opps! A category with this name already exists.',
error: err,
});
}
return next({
status: httpStatus.INTERNAL_SERVER_ERROR,
message: 'Opps! Something is wrong in our server. Please report it to the administrator.',
error: err,
});
}
};
Seed method to insert default categories
A set of default categories should be added when the server run for the first time. To do so, I created a seed method to insert a batch of data in the database if the categories collection is empty.
Related code:
const Category = require('../models/category.model');
// Batch of initial categories
const initialCategories = [
{ key: 'vlog', name: 'VLog' },
{ key: 'graphics', name: 'Graphics' },
{ key: 'art', name: 'Art' },
{ key: 'knack', name: 'Knack' },
{ key: 'onealtruism', name: 'One Altruism' },
{ key: 'music', name: 'Music' },
{ key: 'humor', name: 'Joke/Humor' },
{ key: 'inspiring', name: 'Inspiring' },
{ key: 'visibility', name: 'Visibility' },
{ key: 'news', name: 'News' },
{ key: 'quotes', name: 'Quotes' },
{ key: 'techtrends', name: 'Tech Trends' },
{ key: 'blogposts', name: 'Blog Posts' },
];
/**
* Method to insert the initial batch of categories in the backend
* @public
* @author Jayser Mendez
*/
exports.seedCategories = async () => {
try {
const count = await Category.count();
// If this is the first time running, insert the categories
if (count === 0) {
initialCategories.map(async (category) => {
const newCategory = await new Category({
key: category.key,
name: category.name,
});
await Category.create(newCategory);
});
return true;
}
return false;
} catch (err) {
return false;
}
};