✨ Comments, likes, and notifications
Signed-off-by: Pau Costa <mico@micodev.es>pull/2/head
parent
28e4b5c318
commit
c09b56b0cd
|
|
@ -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'
|
||||||
|
}
|
||||||
|
|
@ -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[]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
Loading…
Reference in New Issue