User listing, getting, following and unfollowing

Signed-off-by: Pau Costa <mico@micodev.es>
pull/2/head
Pau Costa Ferrer 2024-02-03 14:44:44 +01:00
parent 7aa0ebf366
commit 28e4b5c318
8 changed files with 173 additions and 31 deletions

View File

@ -1,53 +1,137 @@
import { AppDataSource } from "../data-source" import { AppDataSource } from "../data-source"
import { NextFunction, Request, Response } from "express" import { NextFunction, Request, Response } from "express"
import { User } from "../entity/User" import { User } from "../entity/User"
import {AppError} from "../util/AppError";
import {AppRequest} from "../util/AppRequest";
import {Notification} from "../entity/Notification";
import {catchAsync} from "../util/catchAsync";
export class UserController { export class UserController {
private userRepository = AppDataSource.getRepository(User) private userRepository = AppDataSource.getRepository(User)
async all(request: Request, response: Response, next: NextFunction) { private notificationRepository = AppDataSource.getRepository(Notification)
return this.userRepository.find() public getAllUsers = catchAsync(async (req: Request, res: Response, next: NextFunction) => {
} const users = await this.userRepository.find()
async one(request: Request, response: Response, next: NextFunction) { // remove sensitive fields
const id = parseInt(request.params.id) users.forEach(user => {
user.deleteSensitiveFields()
})
res.status(200).send(users)
})
public getUser = catchAsync( async (req: Request, res: Response, next: NextFunction) => {
const id = req.params.id
const parsedId = parseInt(id)
// Check if ID is a number
if(isNaN(parsedId)) return next(new AppError('Invalid ID', 400))
const user = await this.userRepository.findOne({where: {id: parsedId}, relations:{
followed: true,
followers: true,
posts: true,
comments: true,
}})
if(!user) return next(new AppError('No user found with that ID', 404))
// remove sensitive fields
user.deleteSensitiveFields()
user.followed.forEach(followedUser => followedUser.deleteSensitiveFields())
user.followers.forEach(follower => follower.deleteSensitiveFields())
return res.send(user)
})
public getMe = catchAsync(async (req: AppRequest, res: Response, next: NextFunction) => {
const user = await this.userRepository.findOne({ const user = await this.userRepository.findOne({
where: { id } where: {id: req.user.id},
relations:{
followed: true,
followers: true,
posts: true,
comments: true,
}
}) })
if (!user) { user.followed.forEach(followedUser => followedUser.deleteSensitiveFields())
return "unregistered user" user.followers.forEach(follower => follower.deleteSensitiveFields())
return res.status(200).send(user)
})
public followUser = catchAsync(async (req: AppRequest, res: Response, next: NextFunction) => {
const userToFollowId = req.params.id
const parsedId = parseInt(userToFollowId)
// Check if ID is a number
if(isNaN(parsedId)) return next(new AppError('Invalid ID', 400))
const user = req.user
const userToFollow = await this.userRepository.findOne({
where: {id: parsedId},
relations:{followed: true, followers: true, notifications: true}}
)
if(!userToFollow) return next(new AppError('No user found with that ID', 404))
// Check if user is already following
if(user.followed.some(followedUser => followedUser.id === userToFollow.id)){
return next(new AppError('You are already following this user', 400))
} }
return user // Follow the user
} user.followed.push(userToFollow)
await this.userRepository.save(user)
// Add the requesting user to the followers of the user being followed
userToFollow.followers.push(user)
// Create a notification for the user being followed
const followNotification = Object.assign(new Notification(),{
seen: false,
message: `${user.firstName} is now following you`,
timeStamp: new Date()
})
userToFollow.notifications.push(
await this.notificationRepository.save(followNotification)
)
await this.userRepository.save(userToFollow)
async save(request: Request, response: Response, next: NextFunction) {
const { firstName, lastName, age } = request.body;
const user = Object.assign(new User(), { return res.status(200).send({
firstName, status: 'success',
lastName, message: `You are now following ${userToFollow.firstName}`
age })
})
public unfollowUser = catchAsync(async (req: AppRequest, res: Response, next: NextFunction) => {
const userToUnfollowId = req.params.id
const parsedId = parseInt(userToUnfollowId)
// Check if ID is a number
if(isNaN(parsedId)) return next(new AppError('Invalid ID', 400))
const user = req.user
const userToUnfollow = await this.userRepository.findOne({
where: {id: parsedId},
relations: {followed: true, followers: true}
}) })
return this.userRepository.save(user) if(!userToUnfollow) return next(new AppError('No user found with that ID', 404))
} // Check if user is following
if(!user.followed.some(followedUser => followedUser.id === userToUnfollow.id)){
async remove(request: Request, response: Response, next: NextFunction) { return next(new AppError('You are not following this user', 400))
const id = parseInt(request.params.id)
let userToRemove = await this.userRepository.findOneBy({ id })
if (!userToRemove) {
return "this user not exist"
} }
// Unfollow the user
user.followed = user.followed.filter(followedUser => followedUser.id !== userToUnfollow.id)
await this.userRepository.save(user)
await this.userRepository.remove(userToRemove) userToUnfollow.followers = userToUnfollow.followers.filter(follower => follower.id !== user.id)
await this.userRepository.save(userToUnfollow)
return "user has been removed" return res.status(200).send({
} status: 'success',
message: `You are no longer following ${userToUnfollow.firstName}`
})
})
} }

View File

@ -106,7 +106,10 @@ export class AuthController {
// Verify the token // Verify the token
const decoded = jwt.verify(token, this.jwt_secret) as JwtPayload const decoded = jwt.verify(token, this.jwt_secret) as JwtPayload
// Check if the user still exists // Check if the user still exists
const candidateUser = await this.userRepository.findOne({where: {id: decoded.id}}) const candidateUser = await this.userRepository.findOne({
where: {id: decoded.id},
relations: {followed: true, followers: true, notifications: true}
})
if(!candidateUser){ if(!candidateUser){
return next(new AppError('The user belonging to this token no longer exists', 401)) return next(new AppError('The user belonging to this token no longer exists', 401))
} }

View File

@ -3,6 +3,7 @@ import { DataSource } from "typeorm"
import { User } from "./entity/User" import { User } from "./entity/User"
import {Comment} from "./entity/Comment"; import {Comment} from "./entity/Comment";
import {Post} from "./entity/Post"; import {Post} from "./entity/Post";
import {Notification} from "./entity/Notification";
export const AppDataSource = new DataSource({ export const AppDataSource = new DataSource({
type: "mysql", type: "mysql",
@ -13,7 +14,7 @@ export const AppDataSource = new DataSource({
database: process.env.MYSQL_DATABASE, database: process.env.MYSQL_DATABASE,
synchronize: true, synchronize: true,
logging: false, logging: false,
entities: [User, Comment, Post], entities: [User, Comment, Post, Notification],
migrations: [], migrations: [],
subscribers: [], subscribers: [],
}) })

View File

@ -0,0 +1,21 @@
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
import {User} from "./User";
@Entity()
export class Notification {
@PrimaryGeneratedColumn()
id: number
@Column()
message: string
@Column()
timeStamp: Date
@Column()
seen: boolean
@ManyToOne(type => User, user => user.notifications)
belongsTo: User
}

View File

@ -2,6 +2,7 @@ import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, OneToMany, JoinTable
import * as bcrypt from "bcrypt" import * as bcrypt from "bcrypt"
import {Post} from "./Post"; import {Post} from "./Post";
import {Comment} from "./Comment"; import {Comment} from "./Comment";
import {Notification} from "./Notification";
@Entity() @Entity()
export class User { export class User {
@ -25,12 +26,19 @@ export class User {
@JoinTable() @JoinTable()
followed: User[] followed: User[]
@ManyToMany(type => User)
@JoinTable()
followers: User[]
@OneToMany(type => Post, post=> post.createdBy) @OneToMany(type => Post, post=> post.createdBy)
posts: Post[] posts: Post[]
@OneToMany(type => Comment, comment=> comment.createdBy) @OneToMany(type => Comment, comment=> comment.createdBy)
comments: Comment[] comments: Comment[]
@OneToMany(type => Notification, notification => notification.belongsTo)
notifications: Notification[]
static async hashPassword(password: string){ static async hashPassword(password: string){
return await bcrypt.hash(password, parseInt(process.env.SALT_ROUNDS)) return await bcrypt.hash(password, parseInt(process.env.SALT_ROUNDS))
@ -39,4 +47,9 @@ export class User {
async comparePassword(password: string){ async comparePassword(password: string){
return await bcrypt.compare(password, this.password) return await bcrypt.compare(password, this.password)
} }
public deleteSensitiveFields(){
delete this.password
delete this.email
}
} }

View File

@ -3,6 +3,8 @@ import * as bodyParser from "body-parser"
import { AppDataSource } from "./data-source" import { AppDataSource } from "./data-source"
import {errorHandler} from "./controller/errorController"; import {errorHandler} from "./controller/errorController";
import {AuthRoutes} from "./routes/authRoutes"; import {AuthRoutes} from "./routes/authRoutes";
import {UserRoutes} from "./routes/userRoutes";
import {AuthController} from "./controller/authController";
AppDataSource.initialize().then(async () => { AppDataSource.initialize().then(async () => {
@ -13,6 +15,11 @@ AppDataSource.initialize().then(async () => {
// register express routes from defined application routes // register express routes from defined application routes
// Auth Routes // Auth Routes
app.use('/auth', AuthRoutes) app.use('/auth', AuthRoutes)
// All routes after this one require authentication
const authController = new AuthController();
app.use(authController.protect)
app.use('/users', UserRoutes)
// setup express app here // setup express app here
app.use(errorHandler) app.use(errorHandler)

View File

@ -7,6 +7,7 @@ export const AuthRoutes = Router();
AuthRoutes.route("/signup").post(authController.handleSignUp) AuthRoutes.route("/signup").post(authController.handleSignUp)
AuthRoutes.route("/login").post(authController.handleLogin) AuthRoutes.route("/login").post(authController.handleLogin)
AuthRoutes.route("/logout").get(authController.handleLogout)

View File

@ -0,0 +1,12 @@
import {Router} from "express";
import {UserController} from "../controller/UserController";
export const UserRoutes = Router();
const userController = new UserController()
UserRoutes.route("/").get(userController.getAllUsers)
UserRoutes.route("/me").get(userController.getMe)
UserRoutes.route("/:id").get(userController.getUser)
UserRoutes.route("/follow/:id").post(userController.followUser)
UserRoutes.route("/unfollow/:id").post(userController.unfollowUser)