Last active
February 1, 2023 15:01
-
-
Save devdbrandy/df7f88b96edd51df71fa94ae774d51bc to your computer and use it in GitHub Desktop.
User followers implementation with Sequelize ORM and ExpressJS
This file contains hidden or 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
import createError from 'http-errors'; | |
import models from '../models'; | |
import Response from '../helpers/responseHandler'; // a wrapper for request.json | |
import { MESSAGE } from '../helpers/constants'; // an object constant used accross the codebase | |
/** | |
* Class handling followers operation | |
* | |
* @class FollowersController | |
*/ | |
class FollowersController { | |
/** | |
* User followers handler | |
* | |
* @static | |
* @param {object} request - Express Request object | |
* @param {object} response - Express Response object | |
* @returns {object} Response object | |
* @param {Function} next - Express NextFunction | |
* @memberof FollowersController | |
*/ | |
static async follow(request, response, next) { | |
const { user, params: { username } } = request; | |
try { | |
const { followable, follower } = await FollowersController.validateFollowable(user, username); | |
await followable.addFollower(follower); | |
return Response.send(response, 200, followable, `${MESSAGE.FOLLOW_SUCCESS} ${username}`); | |
} catch (error) { | |
next(error); | |
} | |
} | |
/** | |
* User unfollow handler | |
* | |
* @static | |
* @param {object} request - Express Request object | |
* @param {object} response - Express Response object | |
* @returns {object} Response object | |
* @param {Function} next - Express NextFunction | |
* @memberof FollowersController | |
*/ | |
static async unfollow(request, response, next) { | |
const { user, params: { username } } = request; | |
try { | |
const { followable, follower } = await FollowersController.validateFollowable(user, username); | |
const existingFollower = await followable.hasFollowers(follower); | |
if (!existingFollower) { | |
next(createError(400, MESSAGE.UNFOLLOW_ERROR)); | |
} | |
await followable.removeFollower(follower); | |
return Response.send(response, 200, followable, `${MESSAGE.UNFOLLOW_SUCCESS} ${username}`); | |
} catch (error) { | |
next(error); | |
} | |
} | |
/** | |
* Validate users to follow | |
* | |
* @static | |
* @param {object} user - Authenticated user object | |
* @param {object} username - Username of the user to follow | |
* @returns {object} Object holding the information of the followable and follower | |
* @memberof FollowersController | |
*/ | |
static async validateFollowable(user, username) { | |
try { | |
const follower = await models.User.findOne({ | |
where: { id: user.id } | |
}); | |
const profile = await models.Profile.findOne({ where: { username } }); | |
if (follower.id === profile.userId) { | |
throw createError(400, MESSAGE.FOLLOW_ERROR); | |
} | |
const followable = await profile.getUser(); | |
if (followable.deletedAt !== null) { | |
throw createError(404, MESSAGE.FOLLOW_ERROR); | |
} | |
return { followable, follower }; | |
} catch (error) { | |
throw error; | |
} | |
} | |
/** | |
* Fetch user followers (handler) | |
* | |
* @static | |
* @param {object} request - Express Request object | |
* @param {object} response - Express Response object | |
* @returns {object} Response object | |
* @param {Function} next - Express NextFunction | |
* @memberof FollowersController | |
*/ | |
static async followers(request, response, next) { | |
const { user } = request; | |
const routePath = request.path.split('/')[2]; | |
let followers; | |
try { | |
const authUser = await models.User.findOne({ | |
where: { id: user.id } | |
}); | |
if (routePath === 'followers') { | |
followers = await authUser.getFollowers(); | |
} else { | |
followers = await authUser.getFollowing(); | |
} | |
return Response.send(response, 400, followers); | |
} catch (error) { | |
next(error); | |
} | |
} | |
} | |
export default FollowersController; |
This file contains hidden or 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
router.post( | |
'/profiles/:username/follow', | |
middlewares.authenticate, | |
followersController.follow | |
); | |
router.post( | |
'/profiles/:username/unfollow', | |
middlewares.authenticate, | |
followersController.unfollow | |
); | |
router.get( | |
'/profiles/followers', | |
middlewares.authenticate, | |
followersController.followers | |
); | |
router.get( | |
'/profiles/following', | |
middlewares.authenticate, | |
followersController.followers | |
); |
This file contains hidden or 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
import bcrypt from 'bcryptjs'; | |
/** | |
* A model class representing user resource | |
* | |
* @param {Sequelize} sequelize - Sequelize object | |
* @param {Sequelize.DataTypes} DataTypes - A convinient object holding data types | |
* @return {Sequelize.Model} - User model | |
*/ | |
export default (sequelize, DataTypes) => { | |
/** | |
* @type {Sequelize.Model} | |
*/ | |
const User = sequelize.define('User', { | |
email: { | |
type: DataTypes.STRING, | |
allowNull: false, | |
unique: true, | |
validate: { | |
isEmail: { msg: 'Must be a valid email address' } | |
} | |
}, | |
password: { | |
type: DataTypes.STRING, | |
set(value) { | |
this.setDataValue('password', bcrypt.hashSync(value, 10)); | |
} | |
}, | |
isConfirmed: { | |
type: DataTypes.BOOLEAN, | |
defaultValue: false | |
}, | |
createdAt: { | |
type: DataTypes.DATE, | |
defaultValue: sequelize.NOW | |
}, | |
updatedAt: { | |
type: DataTypes.DATE, | |
defaultValue: sequelize.NOW, | |
onUpdate: sequelize.NOW | |
}, | |
deletedAt: { | |
allowNull: true, | |
type: DataTypes.DATE, | |
} | |
}, {}); | |
User.associate = (models) => { | |
User.hasOne(models.Profile, { foreignKey: 'userId' }); | |
User.belongsToMany(models.User, { | |
foreignKey: 'userId', | |
as: 'followers', | |
through: models.UserFollowers | |
}); | |
User.belongsToMany(models.User, { | |
foreignKey: 'followerId', | |
as: 'following', | |
through: models.UserFollowers | |
}); | |
}; | |
/** | |
* Validate user password | |
* | |
* @param {Object} user - User instance | |
* @param {string} password - Password to validate | |
* @returns {boolean} Truthy upon successful validation | |
*/ | |
User.comparePassword = (user, password) => bcrypt.compareSync(password, user.password); | |
return User; | |
}; |
thank you so much @devdbrandy, but where i could find it in the docs ?
@mom3d you can look up the Associating Objects
section on the Docs, depending on your version of sequelize.
https://sequelize.org/v5/manual/associations.html#associating-objects
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @mom3d, you can use
joinTableAttributes
option to remove the association attributes from the output, like so:Hope you find this helpful.