✨ User listing, getting, following and unfollowing
Signed-off-by: Pau Costa <mico@micodev.es>pull/2/head
parent
7aa0ebf366
commit
28e4b5c318
|
|
@ -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 user
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(request: Request, response: Response, next: NextFunction) {
|
return res.status(200).send(user)
|
||||||
const { firstName, lastName, age } = request.body;
|
|
||||||
|
|
||||||
const user = Object.assign(new User(), {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
age
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.userRepository.save(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))
|
||||||
}
|
}
|
||||||
|
// 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 remove(request: Request, response: Response, next: NextFunction) {
|
|
||||||
const id = parseInt(request.params.id)
|
|
||||||
|
|
||||||
let userToRemove = await this.userRepository.findOneBy({ id })
|
return res.status(200).send({
|
||||||
|
status: 'success',
|
||||||
|
message: `You are now following ${userToFollow.firstName}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if (!userToRemove) {
|
public unfollowUser = catchAsync(async (req: AppRequest, res: Response, next: NextFunction) => {
|
||||||
return "this user not exist"
|
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}
|
||||||
|
})
|
||||||
|
|
||||||
|
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)){
|
||||||
|
return next(new AppError('You are not following this user', 400))
|
||||||
}
|
}
|
||||||
|
// 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}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: [],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue