Skip to content

Commit

Permalink
add follow and modify fyp page
Browse files Browse the repository at this point in the history
  • Loading branch information
namanhpham committed Sep 10, 2024
1 parent 581bab2 commit 6b20f0f
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 28 deletions.
6 changes: 6 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import indexRouter from './routes/index';
import { config } from 'dotenv';
import { AppDataSource } from './config/data-source';
import handlebarsHelpers from 'handlebars-helpers';
import { User } from './entities/user.entity';
config();

const app = express();
Expand All @@ -37,6 +38,11 @@ hbs.registerHelper('t', (key: string) => {
return i18next.t(key);
});

hbs.registerHelper('isFollowing', function(user: User, followingUsers: Array<User>) {
return followingUsers.some(followingUser => followingUser.userId === user.userId);
});


app.use(
session({
resave: false,
Expand Down
80 changes: 80 additions & 0 deletions src/controllers/follow.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Request, Response } from 'express';
import { FollowService } from '../services/follow.service';
import asyncHandler from 'express-async-handler';
import i18next from 'i18next';
import { isAuthenticated } from '../middlewares/auth.middleware';

const { t } = i18next;
const followService = new FollowService();

// Follow a user
export const followUser = [
isAuthenticated,
asyncHandler(async (req: Request, res: Response) => {
const followerId = req.session.user?.id || 0;
const followedId = parseInt(req.params.userId, 10);

try {
await followService.followUser(followerId, followedId);
req.flash('flashMessage', t('message.followSuccess'));
res.redirect(`/users/${followedId}`);
} catch (error) {
console.error(error);
const err = error as Error;
req.flash('flashMessage', err.message);
res.redirect(`/users/${followedId}`);
}
}),
];

// Unfollow a user
export const unfollowUser = [
isAuthenticated,
asyncHandler(async (req: Request, res: Response) => {
const followerId = req.session.user?.id || 0;
const followedId = parseInt(req.params.userId, 10);

try {
await followService.unfollowUser(followerId, followedId);
req.flash('flashMessage', t('message.unfollowSuccess'));
res.redirect(`/users/${followedId}`);
} catch (error) {
console.error(error);
res.redirect(`/users/${followedId}`);
}
}),
];

// Get followers of a user
export const getFollowers = asyncHandler(async (req: Request, res: Response) => {
const userId = parseInt(req.params.userId, 10);

try {
const followers = await followService.getFollowers(userId);
res.render('user/followers', {
followers,
title: t('title.followers'),
flashMessage: req.flash('flashMessage'),
});
} catch (error) {
console.error(error);
res.status(500).send(t('error.serverError'));
}
});

// Get following users of a user
export const getFollowing = asyncHandler(async (req: Request, res: Response) => {
const userId = parseInt(req.params.userId, 10);

try {
const following = await followService.getFollowing(userId);
res.render('user/following', {
following,
title: t('title.following'),
flashMessage: req.flash('flashMessage'),
});
} catch (error) {
console.error(error);
res.status(500).send(t('error.serverError'));
}
});
21 changes: 19 additions & 2 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import { validateSessionRole,
validateActiveUser,
sanitizeContent } from '../utils/';
import { PostService } from '../services/post.service';
import { FollowService } from '../services/follow.service';
import { User } from '../entities/user.entity';
import { PostVisibility } from '../constants/post-visibility';

const userService = new UserService();
const postService = new PostService();
const followService = new FollowService();

async function validateUserById(req: Request, res: Response) {
const id = parseInt(req.params.id);
Expand All @@ -36,9 +39,18 @@ export const getUsers = asyncHandler(async (req: Request, res: Response) => {
const page = parseInt(req.query.page as string, 10) || 1;
try {
const users = await userService.getAllUsers(page);
const currentUserId = req.session.user?.id;

let followingUsers: User[] = [];
if (req.session.user) {
followingUsers = await followService.getFollowing(req.session.user?.id);
}

res.render('users/index', {
title: t ('title.users'),
users: users,
followingUsers: followingUsers,
currentUserId: currentUserId,
userRole: req.session.user?.role,
userStatus: UserStatus,
isAdmin: validateAdminRole(req),
Expand All @@ -61,8 +73,12 @@ export const getUserById = asyncHandler(async (req: Request, res: Response) => {
const sanitizedPosts = posts.map(post => ({
...post,
content: sanitizeContent(post.content)
}));;
// Admin and page owner can Edit profile.
}));
let isFollowing = false;
if (req.session.user) {
isFollowing = await followService.isFollowing(req.session.user?.id, id);
}

res.render('users/show', {
title: 'title.userProfile',
postVisibility: PostVisibility,
Expand All @@ -72,6 +88,7 @@ export const getUserById = asyncHandler(async (req: Request, res: Response) => {
userRole: req.session.user?.role,
userPosts: sanitizedPosts,
userStatus: UserStatus,
isFollowing: isFollowing,
isOwner: id == req.session.user?.id,
isAdmin: validateAdminRole(req)
});
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"bookmark": "Bookmark",
"latestPost": "See latest posts",
"addNewUser": "Add New User",
"update": "Update"
"update": "Update",
"unfollow": "Unfollow"
},
"content" : {
"username": "Username",
Expand Down
89 changes: 89 additions & 0 deletions src/public/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,93 @@ $(document).ready(function() {
$('#commentForm').toggle();
});

$('.btn-follow').on('click', function() {
const userId = $(this).data('user-id');
const button = $(this);

console.log('Follow button clicked');
console.log('userId:', userId);
console.log('button:', button);

$.ajax({
url: `/follow/follow/${userId}`,
method: 'POST',
success: function() {
alert('User followed successfully');
},
error: function() {
alert('Error following user');
}
});

console.log('Follow button clicked');
});

$('.btn-unfollow').on('click', function() {
const userId = $(this).data('user-id');
const button = $(this);

console.log('Unfollow button clicked');
console.log('userId:', userId);
console.log('button:', button);

$.ajax({
url: `/follow/unfollow/${userId}`,
method: 'POST',
success: function() {
alert('User unfollowed successfully');
},
error: function() {
alert('Error unfollowing user');
}
});

console.log('Unfollow button clicked');
});

$('.unfollow-btn').on('click', function() {
const userId = $(this).data('user-id');
const button = $(this);

console.log('Unfollow button clicked');
console.log('userId:', userId);
console.log('button:', button);

$.ajax({
url: `/follow/unfollow/${userId}`,
method: 'POST',
success: function() {
alert('User unfollowed successfully');
},
error: function() {
alert('Error unfollowing user');
}
});

console.log('Unfollow button clicked');
});

$('.follow-btn').on('click', function() {
const userId = $(this).data('user-id');
const button = $(this);

console.log('Follow button clicked');
console.log('userId:', userId);
console.log('button:', button);

$.ajax({
url: `/follow/follow/${userId}`,
method: 'POST',
success: function() {
alert('User followed successfully');
},
error: function() {
alert('Error following user');
}
});

console.log('Follow button clicked');
});


});
25 changes: 25 additions & 0 deletions src/routes/follow.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// routes/follow.routes.ts

import express from 'express';
import {
followUser,
unfollowUser,
getFollowers,
getFollowing,
} from '../controllers/follow.controller';

const router = express.Router();

// Follow a user
router.post('/follow/:userId', followUser);

// Unfollow a user
router.post('/unfollow/:userId', unfollowUser);

// Get followers of a user
router.get('/:userId/followers', getFollowers);

// Get following users of a user
router.get('/:userId/following', getFollowing);

export default router;
2 changes: 2 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import postRouter from './post.route';
import userRouter from './user.route';
import commentRouter from './comment.route';
import tagRouter from './tag.route';
import followRouter from './follow.route';

const router: Router = Router();

Expand All @@ -17,5 +18,6 @@ router.use('/posts', postRouter);
router.use('/users', userRouter);
router.use('/comments', commentRouter);
router.use('/tags', tagRouter);
router.use('/follow', followRouter);

export default router;
95 changes: 95 additions & 0 deletions src/services/follow.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { AppDataSource } from '../config/data-source';
import { User } from '../entities/user.entity';

export class FollowService {
private userRepository = AppDataSource.getRepository(User);

async followUser(followerId: number, followedId: number): Promise<void> {
if (followerId === followedId) {
throw new Error('You cannot follow yourself');
}

const follower = await this.userRepository.findOne({
where: { userId: followerId },
relations: ['following'],
});

const followed = await this.userRepository.findOne({
where: { userId: followedId },
});

if (!follower || !followed) {
throw new Error('User not found');
}

if (follower.following.some(user => user.userId === followedId)) {
throw new Error('You are already following this user');
}

follower.following.push(followed);
await this.userRepository.save(follower);
}

async unfollowUser(followerId: number, followedId: number): Promise<void> {
if (followerId === followedId) {
throw new Error('You cannot unfollow yourself');
}

const follower = await this.userRepository.findOne({
where: { userId: followerId },
relations: ['following'],
});

if (!follower) {
throw new Error('User not found');
}

follower.following = follower.following.filter(user => user.userId !== followedId);
await this.userRepository.save(follower);
}

async getFollowers(userId: number): Promise<User[]> {
const user = await this.userRepository.findOne({
where: { userId },
relations: ['followers'],
});

if (!user) {
throw new Error('User not found');
}

return user.followers;
}

async getFollowing(userId: number): Promise<User[]> {
const user = await this.userRepository.findOne({
where: { userId },
relations: ['following'],
select: {
following: {
userId: true,
username: true,
}
}
});

if (!user) {
throw new Error('User not found');
}

return user.following;
}

async isFollowing(followerId: number, followedId: number): Promise<boolean> {
const follower = await this.userRepository.findOne({
where: { userId: followerId },
relations: ['following'],
});

if (!follower) {
throw new Error('User not found');
}

return follower.following.some(user => user.userId === followedId);
}
}
Loading

0 comments on commit 6b20f0f

Please sign in to comment.