Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: blog soft delete #1313

Merged
merged 5 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/modules/blogs/blogs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,14 @@ export class BlogController {
@ApiResponse({ status: 200, description: 'Blog fetched successfully.', type: BlogDto })
@ApiResponse({ status: 404, description: 'Blog not found.' })
@ApiResponse({ status: 500, description: 'Internal server error.' })
async getSingleBlog(@Param('id', new ParseUUIDPipe()) id: string, @Request() req): Promise<BlogDto> {
async getSingleBlog(
@Param('id', new ParseUUIDPipe()) id: string,
@Request() req
): Promise<{
status_code: number;
message: string;
data: BlogDto;
}> {
return await this.blogService.getSingleBlog(id, req.user);
}

Expand Down
120 changes: 91 additions & 29 deletions src/modules/blogs/blogs.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as SYS_MSG from '@shared/constants/SystemMessages';
import { HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like, MoreThanOrEqual, FindOptionsWhere } from 'typeorm';
import { Repository, Like, MoreThanOrEqual, FindOptionsWhere, Not, IsNull } from 'typeorm';
import { Blog } from './entities/blog.entity';
import { CreateBlogDto } from './dtos/create-blog.dto';
import { UpdateBlogDto } from './dtos/update-blog.dto';
Expand Down Expand Up @@ -52,8 +52,15 @@ export class BlogService {
};
}

async getSingleBlog(blogId: string, user: User): Promise<any> {
const singleBlog = await this.blogRepository.findOneBy({ id: blogId });
async getSingleBlog(
blogId: string,
user: User
): Promise<{
status_code: number;
message: string;
data: BlogResponseDto;
}> {
const singleBlog = await this.blogRepository.findOneBy({ id: blogId, deletedAt: null });
const fullName = await this.fetchUserById(user.id);

if (!singleBlog) {
Expand All @@ -66,7 +73,7 @@ export class BlogService {
return {
status_code: 200,
message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
data: { blog_id: id, ...rest, author, published_date: created_at },
data: { blog_id: id, ...rest, author, published_date: created_at, created_at },
};
}

Expand Down Expand Up @@ -96,46 +103,56 @@ export class BlogService {
created_at: updatedBlog.created_at,
};
}

async deleteBlogPost(id: string): Promise<void> {
const blog = await this.blogRepository.findOne({ where: { id } });
if (!blog) {
throw new CustomHttpException('Blog post with this id does not exist.', HttpStatus.NOT_FOUND);
} else await this.blogRepository.remove(blog);
} else await this.blogRepository.softRemove(blog);
}

async getAllBlogs(
page: number,
pageSize: number
pageSize: number,
includeDeleted: boolean = false
): Promise<{
status_code: number;
message: string;
data: { currentPage: number; totalPages: number; totalResults: number; blogs: BlogResponseDto[]; meta: any };
}> {
const skip = (page - 1) * pageSize;

const [result, total] = await this.blogRepository.findAndCount({
skip,
take: pageSize,
relations: ['author'],
});

const data = this.mapBlogResults(result);
const totalPages = Math.ceil(total / pageSize);
const result = await this.getBlogs(page, pageSize, includeDeleted ? {} : { deletedAt: null });
return {
status_code: result.status_code,
message: result.message,
data: {
...result.data,
blogs: includeDeleted
? result.data.blogs
: result.data.blogs
.filter(blog => !blog.deletedAt)
.map(blog => {
const { deletedAt, ...rest } = blog;
return rest;
}),
},
};
}

async getDeletedBlogs(
page: number,
pageSize: number
): Promise<{
status_code: number;
message: string;
data: { currentPage: number; totalPages: number; totalResults: number; blogs: BlogResponseDto[]; meta: any };
}> {
const result = await this.getBlogs(page, pageSize, { deletedAt: Not(IsNull()) });
return {
status_code: HttpStatus.OK,
message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
status_code: result.status_code,
message: result.message,
data: {
currentPage: page,
totalPages,
totalResults: total,
blogs: data,
meta: {
hasNext: page < totalPages,
total,
nextPage: page < totalPages ? page + 1 : null,
prevPage: page > 1 ? page - 1 : null,
},
...result.data,
blogs: result.data.blogs,
},
};
}
Expand Down Expand Up @@ -188,7 +205,12 @@ export class BlogService {
current_page: page,
total_pages: totalPages,
total_results: total,
blogs: data,
blogs: data
.filter(blog => !blog.deletedAt)
.map(blog => {
const { deletedAt, ...rest } = blog;
return rest;
}),
meta: {
has_next: page < totalPages,
total,
Expand All @@ -199,6 +221,45 @@ export class BlogService {
};
}

private async getBlogs(
page: number,
pageSize: number,
whereCondition: FindOptionsWhere<Blog>
): Promise<{
status_code: number;
message: string;
data: { currentPage: number; totalPages: number; totalResults: number; blogs: BlogResponseDto[]; meta: any };
}> {
const skip = (page - 1) * pageSize;

const [result, total] = await this.blogRepository.findAndCount({
where: whereCondition,
skip,
take: pageSize,
relations: ['author'],
});

const data = this.mapBlogResults(result);
const totalPages = Math.ceil(total / pageSize);

return {
status_code: HttpStatus.OK,
message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
data: {
currentPage: page,
totalPages,
totalResults: total,
blogs: data,
meta: {
hasNext: page < totalPages,
total,
nextPage: page < totalPages ? page + 1 : null,
prevPage: page > 1 ? page - 1 : null,
},
},
};
}

private buildWhereClause(query: any): FindOptionsWhere<Blog> {
const where: FindOptionsWhere<Blog> = {};

Expand Down Expand Up @@ -249,6 +310,7 @@ export class BlogService {
image_urls: blog.image_urls,
author: author_name,
created_at: blog.created_at,
deletedAt: blog.deletedAt,
};
});
}
Expand Down
6 changes: 6 additions & 0 deletions src/modules/blogs/dtos/blog-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ export class BlogResponseDto {

@ApiProperty({ description: 'The creation date of the blog' })
created_at: Date;

@ApiProperty({ description: 'The published date of the blog' })
published_date?: Date;

@ApiProperty({ description: 'The deletion date of the blog', required: false })
deletedAt?: Date;
}
2 changes: 1 addition & 1 deletion src/modules/blogs/dtos/blog.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export class BlogDto {
author: string;

@ApiProperty({ description: 'The creation date of the blog' })
published_date: Date;
published_date?: Date;
}
8 changes: 7 additions & 1 deletion src/modules/blogs/entities/blog.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Entity, Column, ManyToOne } from 'typeorm';
import { Entity, Column, ManyToOne, DeleteDateColumn, BeforeUpdate } from 'typeorm';
import { AbstractBaseEntity } from '../../../entities/base.entity';
import { User } from '../../user/entities/user.entity';

Expand All @@ -16,6 +16,12 @@ export class Blog extends AbstractBaseEntity {
@Column('simple-array', { nullable: true })
image_urls?: string[];

@DeleteDateColumn({ nullable: true, default: null })
deletedAt?: Date;

@Column({ nullable: true, default: null })
published_date?: Date;

@ManyToOne(() => User, user => user.blogs)
author: User;
}
Loading