Repository: hngprojects/hng_boilerplate_nestjs

Issue:
[FEAT]: Implement Soft Delete Functionality for Blog Posts
## Description

Implement soft delete functionality for the Blog entity. This will allow blog posts to be marked as deleted instead of being permanently removed from the database, enabling potential recovery and maintaining data integrity.

## Acceptance Criteria

-   [x] Add a `deletedAt` column (type `Date`) to the `Blog` entity.
-   [x] Modify the `deleteBlogPost` method in `BlogService` to use `blogRepository.softRemove(blog)` instead of `blogRepository.remove(blog)`.
-   [x] Update `find` and `findOne` queries in `BlogService` (specifically in `getSingleBlog` and `getAllBlogs` methods) to filter out blog posts where `deletedAt` is not `null`.
-   [x] Ensure existing endpoints and functionality remain intact, excluding soft-deleted blogs from standard retrieval.
-   [x] Add a separate endpoint or query parameter (e.g., `includeDeleted=true`) to allow administrators to retrieve soft-deleted blog posts if necessary.
-   [x] Update relevant tests to ensure soft delete functionality works as expected.

## Purpose

Soft delete enhances data integrity by preventing accidental or malicious permanent deletion of blog posts. It allows for potential recovery of deleted content, which is crucial for content management and auditing purposes.

## Requirements

-   Modify the `Blog` entity in `modules/blogs/entities/blog.entity.ts`.
-   Update the `BlogService` methods in `modules/blogs/blogs.service.ts`.
-   Modify existing tests in `modules/blogs/tests/blogs.service.spec.ts`.
-   Potentially add new API endpoints or query parameters to `modules/blogs/blogs.controller.ts`.

## Expected Outcome

Blog posts will be marked as deleted (by setting the `deletedAt` timestamp) instead of being permanently removed from the database. Standard retrieval operations will exclude soft-deleted posts, while administrative access will allow for their retrieval if needed.

## Additional Context

This feature is particularly important for content management systems where data retention and potential recovery are crucial. Implementing soft delete aligns with best practices for data handling and enhances the overall robustness of the blog module.


Discussion:

Solution patch:
diff --git a/src/modules/blogs/blogs.controller.ts b/src/modules/blogs/blogs.controller.ts
index db0a8a36..f7f8ebc0 100644
--- a/src/modules/blogs/blogs.controller.ts
+++ b/src/modules/blogs/blogs.controller.ts
@@ -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);
   }
 
diff --git a/src/modules/blogs/blogs.service.ts b/src/modules/blogs/blogs.service.ts
index c5ee2f6d..e7c8e33c 100644
--- a/src/modules/blogs/blogs.service.ts
+++ b/src/modules/blogs/blogs.service.ts
@@ -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';
@@ -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) {
@@ -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 },
     };
   }
 
@@ -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,
       },
     };
   }
@@ -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,
@@ -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> = {};
 
@@ -249,6 +310,7 @@ export class BlogService {
         image_urls: blog.image_urls,
         author: author_name,
         created_at: blog.created_at,
+        deletedAt: blog.deletedAt,
       };
     });
   }
diff --git a/src/modules/blogs/dtos/blog-response.dto.ts b/src/modules/blogs/dtos/blog-response.dto.ts
index 04814b64..4e17f23a 100644
--- a/src/modules/blogs/dtos/blog-response.dto.ts
+++ b/src/modules/blogs/dtos/blog-response.dto.ts
@@ -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;
 }
diff --git a/src/modules/blogs/dtos/blog.dto.ts b/src/modules/blogs/dtos/blog.dto.ts
index 8a7d914a..02950433 100644
--- a/src/modules/blogs/dtos/blog.dto.ts
+++ b/src/modules/blogs/dtos/blog.dto.ts
@@ -20,5 +20,5 @@ export class BlogDto {
   author: string;
 
   @ApiProperty({ description: 'The creation date of the blog' })
-  published_date: Date;
+  published_date?: Date;
 }
diff --git a/src/modules/blogs/entities/blog.entity.ts b/src/modules/blogs/entities/blog.entity.ts
index 354292ec..7afee40b 100644
--- a/src/modules/blogs/entities/blog.entity.ts
+++ b/src/modules/blogs/entities/blog.entity.ts
@@ -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';
 
@@ -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;
 }



Test patch:
diff --git a/src/modules/blogs/tests/blogs.service.spec.ts b/src/modules/blogs/tests/blogs.service.spec.ts
index c11bf770..5505c564 100644
--- a/src/modules/blogs/tests/blogs.service.spec.ts
+++ b/src/modules/blogs/tests/blogs.service.spec.ts
@@ -3,7 +3,7 @@ import 'reflect-metadata';
 import * as SYS_MSG from '@shared/constants/SystemMessages';
 import { Test, TestingModule } from '@nestjs/testing';
 import { getRepositoryToken } from '@nestjs/typeorm';
-import { Repository, Like, MoreThanOrEqual } from 'typeorm';
+import { Repository, Like, MoreThanOrEqual, Not, IsNull } from 'typeorm';
 import { Blog } from '../entities/blog.entity';
 import { User } from '@modules/user/entities/user.entity';
 import { BlogService } from '../blogs.service';
@@ -24,6 +24,7 @@ describe('BlogService', () => {
     findOneBy: jest.fn(),
     findOne: jest.fn(),
     remove: jest.fn(),
+    softRemove: jest.fn(),
   });
 
   beforeEach(async () => {
@@ -153,6 +154,7 @@ describe('BlogService', () => {
               image_urls: ['http://example.com/image.jpg'],
               author: 'John Doe',
               created_at: new Date('2023-01-01'),
+              deletedAt: undefined,
             },
           ],
           meta: {
@@ -243,7 +245,7 @@ describe('BlogService', () => {
       blog.tags = ['test'];
       blog.image_urls = ['http://example.com/image.jpg'];
       blog.created_at = new Date();
-      blog.updated_at = new Date();
+      blog.deletedAt = null;
 
       jest.spyOn(blogRepository, 'findOneBy').mockResolvedValue(blog);
       jest.spyOn(userRepository, 'findOne').mockResolvedValue(user);
@@ -261,9 +263,11 @@ describe('BlogService', () => {
           image_urls: blog.image_urls,
           published_date: blog.created_at,
           author: 'John Doe',
+          created_at: blog.created_at,
+          deletedAt: blog.deletedAt,
         },
       });
-      expect(blogRepository.findOneBy).toHaveBeenCalledWith({ id: blogId });
+      expect(blogRepository.findOneBy).toHaveBeenCalledWith({ id: blogId, deletedAt: null });
       expect(userRepository.findOne).toHaveBeenCalledWith({
         where: { id: user.id },
         select: ['first_name', 'last_name'],
@@ -290,12 +294,12 @@ describe('BlogService', () => {
       blog.id = 'blog-id';
 
       jest.spyOn(blogRepository, 'findOne').mockResolvedValue(blog);
-      jest.spyOn(blogRepository, 'remove').mockResolvedValue(undefined);
+      jest.spyOn(blogRepository, 'softRemove').mockResolvedValue(undefined);
 
       await service.deleteBlogPost('blog-id');
 
       expect(blogRepository.findOne).toHaveBeenCalledWith({ where: { id: 'blog-id' } });
-      expect(blogRepository.remove).toHaveBeenCalledWith(blog);
+      expect(blogRepository.softRemove).toHaveBeenCalledWith(blog);
     });
 
     it('should throw a 404 error if blog not found', async () => {
@@ -304,4 +308,552 @@ describe('BlogService', () => {
       await expect(service.deleteBlogPost('blog-id')).rejects.toThrow('Blog post with this id does not exist');
     });
   });
+
+  describe('getAllBlogs', () => {
+    it('should return all blogs excluding deleted', async () => {
+      const blog = new Blog();
+      blog.id = 'blog-id';
+      blog.title = 'Test Blog';
+      blog.content = 'Test Content';
+      blog.tags = ['test'];
+      blog.image_urls = ['http://example.com/image.jpg'];
+      blog.author = new User();
+      blog.author.first_name = 'John';
+      blog.author.last_name = 'Doe';
+      blog.created_at = new Date();
+      blog.updated_at = new Date();
+
+      const expectedResponse = {
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 1,
+          blogs: [
+            {
+              blog_id: 'blog-id',
+              title: 'Test Blog',
+              content: 'Test Content',
+              tags: ['test'],
+              image_urls: ['http://example.com/image.jpg'],
+              author: 'John Doe',
+              created_at: blog.created_at,
+              deletedAt: undefined,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 1,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      };
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog], 1]);
+
+      const result = await service.getAllBlogs(1, 10);
+
+      expect(result).toEqual(expectedResponse);
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: null },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should return all blogs including deleted', async () => {
+      const blog = new Blog();
+      blog.id = 'blog-id';
+      blog.title = 'Test Blog';
+      blog.content = 'Test Content';
+      blog.tags = ['test'];
+      blog.image_urls = ['http://example.com/image.jpg'];
+      blog.author = new User();
+      blog.author.first_name = 'John';
+      blog.author.last_name = 'Doe';
+      blog.created_at = new Date();
+      blog.updated_at = new Date();
+      blog.deletedAt = new Date();
+
+      const expectedResponse = {
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 1,
+          blogs: [
+            {
+              blog_id: 'blog-id',
+              title: 'Test Blog',
+              content: 'Test Content',
+              tags: ['test'],
+              image_urls: ['http://example.com/image.jpg'],
+              author: 'John Doe',
+              created_at: blog.created_at,
+              deletedAt: blog.deletedAt,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 1,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      };
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog], 1]);
+
+      const result = await service.getAllBlogs(1, 10, true);
+
+      expect(result).toEqual(expectedResponse);
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: {},
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should return an empty list if no blogs are found', async () => {
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[], 0]);
+
+      const result = await service.getAllBlogs(1, 10);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 0,
+          totalResults: 0,
+          blogs: [],
+          meta: {
+            hasNext: false,
+            total: 0,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: null },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should handle pagination correctly', async () => {
+      const blog = new Blog();
+      blog.id = 'blog-id';
+      blog.title = 'Test Blog';
+      blog.content = 'Test Content';
+      blog.tags = ['test'];
+      blog.image_urls = ['http://example.com/image.jpg'];
+      blog.author = new User();
+      blog.author.first_name = 'John';
+      blog.author.last_name = 'Doe';
+      blog.created_at = new Date();
+      blog.updated_at = new Date();
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog], 1]);
+
+      const result = await service.getAllBlogs(2, 1);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 2,
+          totalPages: 1,
+          totalResults: 1,
+          blogs: [
+            {
+              blog_id: 'blog-id',
+              title: 'Test Blog',
+              content: 'Test Content',
+              tags: ['test'],
+              image_urls: ['http://example.com/image.jpg'],
+              author: 'John Doe',
+              created_at: blog.created_at,
+              deletedAt: undefined,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 1,
+            nextPage: null,
+            prevPage: 1,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: null },
+        skip: 1,
+        take: 1,
+        relations: ['author'],
+      });
+    });
+
+    it('should return all blogs excluding deleted with multiple mock data', async () => {
+      const blog1 = new Blog();
+      blog1.id = 'blog-id-1';
+      blog1.title = 'Test Blog 1';
+      blog1.content = 'Test Content 1';
+      blog1.tags = ['test1'];
+      blog1.image_urls = ['http://example.com/image1.jpg'];
+      blog1.author = new User();
+      blog1.author.first_name = 'John';
+      blog1.author.last_name = 'Doe';
+      blog1.created_at = new Date();
+      blog1.updated_at = new Date();
+      blog1.deletedAt = null;
+
+      const blog2 = new Blog();
+      blog2.id = 'blog-id-2';
+      blog2.title = 'Test Blog 2';
+      blog2.content = 'Test Content 2';
+      blog2.tags = ['test2'];
+      blog2.image_urls = ['http://example.com/image2.jpg'];
+      blog2.author = new User();
+      blog2.author.first_name = 'Jane';
+      blog2.author.last_name = 'Doe';
+      blog2.created_at = new Date();
+      blog2.updated_at = new Date();
+      blog2.deletedAt = new Date();
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog1, blog2], 2]);
+
+      const result = await service.getAllBlogs(1, 10);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 2,
+          blogs: [
+            {
+              blog_id: 'blog-id-1',
+              title: 'Test Blog 1',
+              content: 'Test Content 1',
+              tags: ['test1'],
+              image_urls: ['http://example.com/image1.jpg'],
+              author: 'John Doe',
+              created_at: blog1.created_at,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 2,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: null },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should return all blogs including deleted with multiple mock data', async () => {
+      const blog1 = new Blog();
+      blog1.id = 'blog-id-1';
+      blog1.title = 'Test Blog 1';
+      blog1.content = 'Test Content 1';
+      blog1.tags = ['test1'];
+      blog1.image_urls = ['http://example.com/image1.jpg'];
+      blog1.author = new User();
+      blog1.author.first_name = 'John';
+      blog1.author.last_name = 'Doe';
+      blog1.created_at = new Date();
+      blog1.updated_at = new Date();
+      blog1.deletedAt = null;
+
+      const blog2 = new Blog();
+      blog2.id = 'blog-id-2';
+      blog2.title = 'Test Blog 2';
+      blog2.content = 'Test Content 2';
+      blog2.tags = ['test2'];
+      blog2.image_urls = ['http://example.com/image2.jpg'];
+      blog2.author = new User();
+      blog2.author.first_name = 'Jane';
+      blog2.author.last_name = 'Doe';
+      blog2.created_at = new Date();
+      blog2.updated_at = new Date();
+      blog2.deletedAt = new Date();
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog1, blog2], 2]);
+
+      const result = await service.getAllBlogs(1, 10, true);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 2,
+          blogs: [
+            {
+              blog_id: 'blog-id-1',
+              title: 'Test Blog 1',
+              content: 'Test Content 1',
+              tags: ['test1'],
+              image_urls: ['http://example.com/image1.jpg'],
+              author: 'John Doe',
+              created_at: blog1.created_at,
+              deletedAt: null,
+            },
+            {
+              blog_id: 'blog-id-2',
+              title: 'Test Blog 2',
+              content: 'Test Content 2',
+              tags: ['test2'],
+              image_urls: ['http://example.com/image2.jpg'],
+              author: 'Jane Doe',
+              created_at: blog2.created_at,
+              deletedAt: blog2.deletedAt,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 2,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: {},
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+  });
+
+  describe('getDeletedBlogs', () => {
+    it('should return all deleted blogs', async () => {
+      const blog = new Blog();
+      blog.id = 'blog-id';
+      blog.title = 'Test Blog';
+      blog.content = 'Test Content';
+      blog.tags = ['test'];
+      blog.image_urls = ['http://example.com/image.jpg'];
+      blog.author = new User();
+      blog.author.first_name = 'John';
+      blog.author.last_name = 'Doe';
+      blog.created_at = new Date();
+      blog.updated_at = new Date();
+      blog.deletedAt = new Date();
+
+      const expectedResponse = {
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 1,
+          blogs: [
+            {
+              blog_id: 'blog-id',
+              title: 'Test Blog',
+              content: 'Test Content',
+              tags: ['test'],
+              image_urls: ['http://example.com/image.jpg'],
+              author: 'John Doe',
+              created_at: blog.created_at,
+              deletedAt: blog.deletedAt,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 1,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      };
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog], 1]);
+
+      const result = await service.getDeletedBlogs(1, 10);
+
+      expect(result).toEqual(expectedResponse);
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: Not(IsNull()) },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should return an empty list if no deleted blogs are found', async () => {
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[], 0]);
+
+      const result = await service.getDeletedBlogs(1, 10);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 0,
+          totalResults: 0,
+          blogs: [],
+          meta: {
+            hasNext: false,
+            total: 0,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: Not(IsNull()) },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+
+    it('should handle pagination correctly for deleted blogs', async () => {
+      const blog = new Blog();
+      blog.id = 'blog-id';
+      blog.title = 'Test Blog';
+      blog.content = 'Test Content';
+      blog.tags = ['test'];
+      blog.image_urls = ['http://example.com/image.jpg'];
+      blog.author = new User();
+      blog.author.first_name = 'John';
+      blog.author.last_name = 'Doe';
+      blog.created_at = new Date();
+      blog.updated_at = new Date();
+      blog.deletedAt = new Date();
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog], 1]);
+
+      const result = await service.getDeletedBlogs(2, 1);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 2,
+          totalPages: 1,
+          totalResults: 1,
+          blogs: [
+            {
+              blog_id: 'blog-id',
+              title: 'Test Blog',
+              content: 'Test Content',
+              tags: ['test'],
+              image_urls: ['http://example.com/image.jpg'],
+              author: 'John Doe',
+              created_at: blog.created_at,
+              deletedAt: blog.deletedAt,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 1,
+            nextPage: null,
+            prevPage: 1,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: Not(IsNull()) },
+        skip: 1,
+        take: 1,
+        relations: ['author'],
+      });
+    });
+
+    it('should return all deleted blogs with multiple mock data', async () => {
+      const blog1 = new Blog();
+      blog1.id = 'blog-id-1';
+      blog1.title = 'Test Blog 1';
+      blog1.content = 'Test Content 1';
+      blog1.tags = ['test1'];
+      blog1.image_urls = ['http://example.com/image1.jpg'];
+      blog1.author = new User();
+      blog1.author.first_name = 'John';
+      blog1.author.last_name = 'Doe';
+      blog1.created_at = new Date();
+      blog1.updated_at = new Date();
+      blog1.deletedAt = new Date();
+
+      const blog2 = new Blog();
+      blog2.id = 'blog-id-2';
+      blog2.title = 'Test Blog 2';
+      blog2.content = 'Test Content 2';
+      blog2.tags = ['test2'];
+      blog2.image_urls = ['http://example.com/image2.jpg'];
+      blog2.author = new User();
+      blog2.author.first_name = 'Jane';
+      blog2.author.last_name = 'Doe';
+      blog2.created_at = new Date();
+      blog2.updated_at = new Date();
+      blog2.deletedAt = new Date();
+
+      jest.spyOn(blogRepository, 'findAndCount').mockResolvedValue([[blog1, blog2], 2]);
+
+      const result = await service.getDeletedBlogs(1, 10);
+
+      expect(result).toEqual({
+        status_code: 200,
+        message: SYS_MSG.BLOG_FETCHED_SUCCESSFUL,
+        data: {
+          currentPage: 1,
+          totalPages: 1,
+          totalResults: 2,
+          blogs: [
+            {
+              blog_id: 'blog-id-1',
+              title: 'Test Blog 1',
+              content: 'Test Content 1',
+              tags: ['test1'],
+              image_urls: ['http://example.com/image1.jpg'],
+              author: 'John Doe',
+              created_at: blog1.created_at,
+              deletedAt: blog1.deletedAt,
+            },
+            {
+              blog_id: 'blog-id-2',
+              title: 'Test Blog 2',
+              content: 'Test Content 2',
+              tags: ['test2'],
+              image_urls: ['http://example.com/image2.jpg'],
+              author: 'Jane Doe',
+              created_at: blog2.created_at,
+              deletedAt: blog2.deletedAt,
+            },
+          ],
+          meta: {
+            hasNext: false,
+            total: 2,
+            nextPage: null,
+            prevPage: null,
+          },
+        },
+      });
+      expect(blogRepository.findAndCount).toHaveBeenCalledWith({
+        where: { deletedAt: Not(IsNull()) },
+        skip: 0,
+        take: 10,
+        relations: ['author'],
+      });
+    });
+  });
 });