Skip to content

Instantly share code, notes, and snippets.

@devdbrandy
Last active February 1, 2023 15:01
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save devdbrandy/df7f88b96edd51df71fa94ae774d51bc to your computer and use it in GitHub Desktop.
Save devdbrandy/df7f88b96edd51df71fa94ae774d51bc to your computer and use it in GitHub Desktop.
User followers implementation with Sequelize ORM and ExpressJS
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;
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
);
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;
};
@mom3d
Copy link

mom3d commented Jan 4, 2022

thank you so much @devdbrandy, but where i could find it in the docs ?

@devdbrandy
Copy link
Author

devdbrandy commented Jan 4, 2022

@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