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-category): add search functionality to BlogCategoryService #1342

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
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
10,557 changes: 4,229 additions & 6,328 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
"premigration:run": "npm run build",
"migration:run": "npx typeorm migration:run -d dist/src/database/data-source",
"migration:revert": "npx typeorm migration:revert -d dist/src/database/data-source",
"seed": "ts-node src/seed.ts",
"seed": "ts-node src/database/seeding/seeding.service.ts",
"postinstall": "npm install --platform=linux --arch=x64 sharp"
},
"dependencies": {

"@faker-js/faker": "^8.4.1",
"@google/generative-ai": "^0.22.0",
"@nestjs/axios": "^4.0.0",
"@nestjs/bull": "^11.0.2",
Expand All @@ -47,7 +47,6 @@
"@nestjs/swagger": "^11.0.5",
"@nestjs/typeorm": "^11.0.0",
"@types/nodemailer": "^6.4.17",

"@types/speakeasy": "^2.0.10",
"@vitalets/google-translate-api": "^9.2.1",
"aws-sdk": "^2.1692.0",
Expand Down
10 changes: 3 additions & 7 deletions src/database/migrations/1740788110830-UpdatePhoneNumberType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm';

export class UpdatePhoneNumberType1740788110830 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE contact ALTER COLUMN phone TYPE VARCHAR(20) USING phone::text`
);
await queryRunner.query(`ALTER TABLE contact ALTER COLUMN phone TYPE VARCHAR(20) USING phone::text`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE contact ALTER COLUMN phone TYPE INTEGER USING phone::integer`
);
await queryRunner.query(`ALTER TABLE contact ALTER COLUMN phone TYPE INTEGER USING phone::integer`);
}
}
}
12 changes: 11 additions & 1 deletion src/modules/blog-category/blog-category.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Post, UseGuards, Patch, Param, Delete } from '@nestjs/common';
import { Body, Controller, Post, UseGuards, Patch, Param, Delete, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { BlogCategoryService } from './blog-category.service';
import { SuperAdminGuard } from '@guards/super-admin.guard';
Expand Down Expand Up @@ -52,4 +52,14 @@ export class BlogCategoryController {
async deleteBlogCategory(@Param('id') id: string) {
return await this.blogCategoryService.deleteOrganisationCategory(id);
}

@Get('search')
@ApiOperation({ summary: 'Search blog categories by name' })
@ApiResponse({ status: 200, description: 'Categories found' })
@ApiResponse({ status: 404, description: 'No categories found' })
async searchCategories(@Query('term') searchTerm: string) {
console.log('Search Term: ', searchTerm);
const result = await this.blogCategoryService.searchCategories(searchTerm);
return result;
}
}
33 changes: 33 additions & 0 deletions src/modules/blog-category/blog-category.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,37 @@ export class BlogCategoryService {
message: 'Organisation category deleted successfully',
};
}

async searchCategories(searchTerm: string): Promise<{
status: string;
status_code: number;
message: string;
data: { categories: BlogCategory[]; total: number };
}> {
// Handle empty search term
if (!searchTerm || searchTerm.trim() === '') {
return {
status: 'success',
status_code: 200,
message: 'No search term provided',
data: { categories: [], total: 0 },
};
}

// Perform a case-insensitive search
const categories = await this.blogCategoryRepository
.createQueryBuilder('category')
.where('LOWER(category.name) LIKE LOWER(:searchTerm)', {
searchTerm: `%${searchTerm}%`,
})
.getMany();

// Return the results in the desired format
return {
status: 'success',
status_code: 200,
message: categories.length > 0 ? 'Categories found successfully' : 'No categories found',
data: { categories, total: categories.length },
};
}
}
62 changes: 62 additions & 0 deletions src/modules/blog-category/tests/blog-category.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,66 @@ describe('BlogCategoryService', () => {
expect(repository.findOne).toHaveBeenCalledWith({ where: { id: 'blog-id' } });
expect(repository.remove).toHaveBeenCalledWith(blogCategory);
});

it('should return empty array and total 0 for empty search term', async () => {
const result = await service.searchCategories('');
expect(result).toEqual({
status: 'success',
status_code: 200,
message: 'No search term provided',
data: { categories: [], total: 0 },
});
});

it('should return matching categories for partial search term', async () => {
const mockCategories = [
{ id: '1', name: 'Technology' },
{ id: '2', name: 'Tech News' },
];

jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
where: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue(mockCategories),
} as any);

const result = await service.searchCategories('tech');
expect(result).toEqual({
status: 'success',
status_code: 200,
message: 'Categories found successfully',
data: { categories: mockCategories, total: 2 },
});
});

it('should return empty array and total 0 for no matches', async () => {
jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
where: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue([]),
} as any);

const result = await service.searchCategories('nonexistent');
expect(result).toEqual({
status: 'success',
status_code: 200,
message: 'No categories found',
data: { categories: [], total: 0 },
});
});

it('should handle search term with special characters', async () => {
const mockCategories = [{ id: '1', name: 'C# Programming' }];

jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({
where: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue(mockCategories),
} as any);

const result = await service.searchCategories('C#');
expect(result).toEqual({
status: 'success',
status_code: 200,
message: 'Categories found successfully',
data: { categories: mockCategories, total: 1 },
});
});
});
2 changes: 1 addition & 1 deletion src/modules/contact-us/contact-us.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ export class ContactUsService {
},
});
}
}
}
32 changes: 16 additions & 16 deletions src/modules/contact-us/dto/create-contact-us.dto.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { IsEmail, IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator';

export class CreateContactDto {
@IsNotEmpty({ message: 'Name should not be empty' })
@IsString({ message: 'Name must be a string' })
name: string;
@IsNotEmpty({ message: 'Name should not be empty' })
@IsString({ message: 'Name must be a string' })
name: string;

@IsNotEmpty({ message: 'Email should not be empty' })
@IsEmail({}, { message: 'Email must be valid' })
email: string;
@IsNotEmpty({ message: 'Email should not be empty' })
@IsEmail({}, { message: 'Email must be valid' })
email: string;

@IsOptional()
@IsString({ message: 'Phone must be a string' })
@Matches(/^\+?[0-9\-\s()]{8,20}$/, {
message: 'Invalid phone number format'
})
phone: string;
@IsOptional()
@IsString({ message: 'Phone must be a string' })
@Matches(/^\+?[0-9\-\s()]{8,20}$/, {
message: 'Invalid phone number format',
})
phone: string;

@IsNotEmpty({ message: 'Message should not be empty' })
@IsString({ message: 'Message must be a string' })
message: string;
}
@IsNotEmpty({ message: 'Message should not be empty' })
@IsString({ message: 'Message must be a string' })
message: string;
}
22 changes: 11 additions & 11 deletions src/modules/contact-us/entities/contact-us.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { Entity, Column } from 'typeorm';

@Entity()
export class ContactUs extends AbstractBaseEntity {
@Column('varchar', { length: 20, nullable: true })
phone: string;
@Column('varchar', { length: 20, nullable: true })
phone: string;

@Column('varchar', { nullable: false })
name: string;
@Column('varchar', { nullable: false })
name: string;

@Column('varchar', { nullable: false })
email: string;
@Column('varchar', { nullable: false })
email: string;

@Column('text', { nullable: false })
message: string;
@Column('text', { nullable: false })
message: string;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}
Loading