Comments, likes, and notifications

Signed-off-by: Pau Costa <mico@micodev.es>
pull/2/head
Pau Costa Ferrer 2024-02-04 12:30:11 +01:00
parent 28e4b5c318
commit c09b56b0cd
7 changed files with 280 additions and 3 deletions

View File

@ -0,0 +1,235 @@
import {AppDataSource} from "../data-source";
import {User} from "../entity/User";
import {Post} from "../entity/Post";
import {Comment} from "../entity/Comment";
import {Notification} from "../entity/Notification";
import {catchAsync} from "../util/catchAsync";
import {AppError} from "../util/AppError";
import {AppRequest} from "../util/AppRequest";
export class PostController {
private postRepository = AppDataSource.getRepository(Post)
private commentRepository = AppDataSource.getRepository(Comment)
private notificationRepository = AppDataSource.getRepository(Notification)
private userRepository = AppDataSource.getRepository(User)
public getAllPosts = catchAsync(async (_req, res, _next) => {
const posts = await this.postRepository.find({relations: {createdBy: true}})
// Remove sensitive fields
posts.forEach(post => {
post.deleteSensitiveFields()
})
res.status(200).send(posts)
})
public getPost = catchAsync(async (req, res, next) => {
const { post, errorMessage} =
await this.validateRequestAndGetEntities(req)
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID'){
return next(new AppError('No post found with that ID', 404))
}
// Remove sensitive fields
post.deleteSensitiveFields()
res.send(post)
})
public getFollowedPosts = catchAsync(async (req : AppRequest, res, _next) => {
const user = await this.userRepository.findOne({
where: {id: req.user.id},
relations: {followed: true, posts: true}})
const followedPosts = user.followed.map(followedUser => followedUser.posts).flat()
// Remove sensitive fields
followedPosts.forEach(post => {
post.deleteSensitiveFields()
})
res.send(followedPosts)
})
public createPost = catchAsync(async (req : AppRequest, res, next) => {
const user = await this.userRepository.findOne({where: {id: req.user.id}})
const {title, content} = req.body
if(!title || !content) return next(new AppError('Title and content are required', 400))
const newPost = Object.assign(new Post(), {
title,
content,
createdBy: user,
createdAt: new Date()})
const post = await this.postRepository.save(newPost)
// Remove sensitive fields
post.deleteSensitiveFields()
res.status(201).send(post)
})
public updatePost = catchAsync(async (req : AppRequest, res, next) => {
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
const {title, content} = req.body
if(!title || !content) return next(new AppError('Title and content are required', 400))
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID'){
return next(new AppError('No post found with that ID', 404))
}
if(post.createdBy.id !== user.id){
return next(new AppError('You are not authorized to update this post', 403))
}
post.title = title
post.content = content
await this.postRepository.save(post)
// Remove sensitive fields
post.deleteSensitiveFields()
res.status(200).send(post)
})
public deletePost = catchAsync(async (req : AppRequest, res, next) => {
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID') return next(new AppError('No post found with that ID', 404))
if(post.createdBy.id !== user.id){
return next(new AppError('You are not authorized to delete this post', 403))
}
await this.postRepository.remove(post)
res.status(204).send()
})
public likePost = catchAsync(async (req : AppRequest, res, next) => {
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID'){
return next(new AppError('No post found with that ID', 404))
}
// Check if user has already liked the post
if(post.likedBy.some(likedUser => likedUser.id === user.id)){
return next(new AppError('You have already liked this post', 400))
}
post.likedBy.push(user)
await this.postRepository.save(post)
// Send notification to post creator
const newNotification = Object.assign(new Notification(),{
message: `${user.firstName} ${user.lastName} liked your post`,
belongsTo: post.createdBy,
timeStamp: new Date()
})
const notification = await this.notificationRepository.save(newNotification)
post.createdBy.notifications.push(notification)
await this.userRepository.save(post.createdBy)
// Remove sensitive fields
post.deleteSensitiveFields()
res.status(200).send(post)
})
public unlikePost = catchAsync(async (req : AppRequest, res, next) => {
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID') return next(new AppError('No post found with that ID', 404))
// Check if user likes the post
if(!post.likedBy.some(likedUser => likedUser.id === user.id)){
return next(new AppError('You have not liked this post', 400))
}
post.likedBy = post.likedBy.filter(likedUser => likedUser.id !== user.id)
await this.postRepository.save(post)
// Remove sensitive fields
post.deleteSensitiveFields()
res.status(200).send(post)
})
public commentPost = catchAsync(async (req : AppRequest, res, next) => {
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
if(errorMessage == 'No post found with that ID') {
return next(new AppError('No post found with that ID', 404))
}
const {content} = req.body
const newComment = Object.assign(new Comment(), {
content,
createdBy: user,
post,
createdAt: new Date()
})
const comment = await this.commentRepository.save(newComment)
// Send notification to post creator
const newNotification = Object.assign(new Notification(),{
message: `${user.firstName} ${user.lastName} commented on your post`,
belongsTo: post.createdBy,
timeStamp: new Date()
})
await this.notificationRepository.save(newNotification)
// Remove sensitive fields
comment.createdBy.deleteSensitiveFields()
post.deleteSensitiveFields()
res.status(201).send(comment)
})
private validateRequestAndGetEntities = async (req : AppRequest,) : Promise<validatedEntities> => {
const postId = req.params.id
const parsedId = parseInt(postId)
let errorMessage: 'Invalid ID' | 'No post found with that ID'
// Check if ID is a number
if(isNaN(parsedId)) errorMessage = 'Invalid ID'
const user = req.user
const post = await this.postRepository.findOne({
where: {id: parsedId},
relations: {
likedBy: true,
comments: { createdBy: true },
createdBy: {notifications: true}
}
})
// Check if post exists
if(!post) errorMessage = 'No post found with that ID'
return {user, post, errorMessage}
}
}
interface validatedEntities {
user: User
post: Post
errorMessage?: 'Invalid ID' | 'No post found with that ID'
}

View File

@ -1,5 +1,6 @@
import {Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn} from "typeorm"; import {Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn} from "typeorm";
import {User} from "./User"; import {User} from "./User";
import {Post} from "./Post";
@Entity() @Entity()
export class Comment { export class Comment {
@ -12,9 +13,12 @@ export class Comment {
@Column() @Column()
createdAt: Date createdAt: Date
@ManyToOne(() => User, user => user.posts) @ManyToOne(() => User, user => user.comments)
createdBy: User createdBy: User
@ManyToOne(() => Post, post => post.comments)
post: Post
@ManyToMany(()=> User) @ManyToMany(()=> User)
@JoinTable() @JoinTable()
likedBy: User[] likedBy: User[]

View File

@ -13,7 +13,7 @@ export class Notification {
@Column() @Column()
timeStamp: Date timeStamp: Date
@Column() @Column({default: false})
seen: boolean seen: boolean
@ManyToOne(type => User, user => user.notifications) @ManyToOne(type => User, user => user.notifications)

View File

@ -1,5 +1,6 @@
import {Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn} from "typeorm"; import {Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn} from "typeorm";
import {User} from "./User"; import {User} from "./User";
import {Comment} from "./Comment";
@Entity() @Entity()
export class Post { export class Post {
@ -18,8 +19,22 @@ export class Post {
@ManyToOne(() => User, user => user.posts) @ManyToOne(() => User, user => user.posts)
createdBy: User createdBy: User
@OneToMany(() => Comment, comment => comment.post)
comments: Comment[]
@ManyToMany(()=> User) @ManyToMany(()=> User)
@JoinTable() @JoinTable()
likedBy: User[] likedBy: User[]
public deleteSensitiveFields(){
this.createdBy.deleteSensitiveFields()
if(this.likedBy){
this.likedBy.forEach(user => user.deleteSensitiveFields())
}
if(this.comments){
this.comments.forEach(comment => comment.createdBy.deleteSensitiveFields())
}
}
} }

View File

@ -51,5 +51,8 @@ export class User {
public deleteSensitiveFields(){ public deleteSensitiveFields(){
delete this.password delete this.password
delete this.email delete this.email
delete this.notifications
delete this.followed
delete this.followers
} }
} }

View File

@ -5,6 +5,7 @@ import {errorHandler} from "./controller/errorController";
import {AuthRoutes} from "./routes/authRoutes"; import {AuthRoutes} from "./routes/authRoutes";
import {UserRoutes} from "./routes/userRoutes"; import {UserRoutes} from "./routes/userRoutes";
import {AuthController} from "./controller/authController"; import {AuthController} from "./controller/authController";
import {PostRoutes} from "./routes/postRoutes";
AppDataSource.initialize().then(async () => { AppDataSource.initialize().then(async () => {
@ -20,6 +21,7 @@ AppDataSource.initialize().then(async () => {
app.use(authController.protect) app.use(authController.protect)
app.use('/users', UserRoutes) app.use('/users', UserRoutes)
app.use('/posts', PostRoutes)
// setup express app here // setup express app here
app.use(errorHandler) app.use(errorHandler)

View File

@ -0,0 +1,18 @@
import {PostController} from "../controller/postController";
import {Router} from "express";
const postController = new PostController()
export const PostRoutes = Router();
PostRoutes.route("/")
.get(postController.getAllPosts)
.post(postController.createPost)
PostRoutes.route("/followed").get(postController.getFollowedPosts)
PostRoutes.route("/:id")
.get(postController.getPost)
.patch(postController.updatePost)
.delete(postController.deletePost)
PostRoutes.route("/:id/like").post(postController.likePost)
PostRoutes.route("/:id/unlike").post(postController.unlikePost)
PostRoutes.route("/:id/comment").post(postController.commentPost)