Skip to content

Commit

Permalink
feat(exercises): 🔍 added autocomplete suggestion search
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberboyanmol committed Aug 24, 2024
1 parent e639f2c commit a08f6d3
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 1 deletion.
54 changes: 54 additions & 0 deletions src/modules/exercises/controllers/exercise.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,59 @@ export class ExerciseController implements Routes {
})
}
)
// autocomplete endpoint
this.controller.openapi(
createRoute({
method: 'get',
path: '/exercises/autocomplete',
tags: ['Exercises'],
summary: 'Autocomplete Exercise Search',
description:
'Retrieves a list of exercise names that match the search term using fuzzy search. This endpoint provides autocomplete suggestions based on the MongoDB autocomplete feature, helping users find exercises quickly as they type.',
operationId: 'autocompleteExercises',
request: {
query: z.object({
search: z.string().optional().openapi({
title: 'Search Term',
description:
'A string used to filter exercises based on a search term. Supports fuzzy matching to suggest relevant exercise names as the user types.',
type: 'string',
example: 'cardio',
default: ''
})
})
},
responses: {
200: {
description: 'Successful response with a list of exercise name suggestions.',
content: {
'application/json': {
schema: z.object({
success: z.boolean().openapi({
description: 'Indicates whether the request was successful.',
type: 'boolean',
example: true
}),
data: z.array(z.string()).openapi({
description: 'Array of suggested exercise names based on the search term.'
})
})
}
}
},
500: {
description: 'Internal server error'
}
}
}),
async (ctx) => {
const { search } = ctx.req.valid('query')
const response = await this.exerciseService.getAutoCompleteSuggestions({ search })
return ctx.json({
success: true,
data: response
})
}
)
}
}
9 changes: 9 additions & 0 deletions src/modules/exercises/services/exercise.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { IExerciseModel } from '#infra/mongodb/models/exercises/exercise.entity.js'
import { CreateExerciseArgs, CreateExerciseUseCase } from '../use-cases/create-exercise'
import {
GetAutoCompleteSuggestionsArgs,
GetAutoCompleteSuggestionsUseCase
} from '../use-cases/get-autocomplete-suggestions'
import { GetExerciseArgs, GetExercisesUseCase } from '../use-cases/get-exercises/get-exercise.usecase'

export class ExerciseService {
private readonly createExerciseUseCase: CreateExerciseUseCase
private readonly getExercisesUseCase: GetExercisesUseCase
private readonly getAutoCompleteSuggestionsUseCase: GetAutoCompleteSuggestionsUseCase
constructor(private readonly exerciseModel: IExerciseModel) {
this.createExerciseUseCase = new CreateExerciseUseCase(exerciseModel)
this.getExercisesUseCase = new GetExercisesUseCase(exerciseModel)
this.getAutoCompleteSuggestionsUseCase = new GetAutoCompleteSuggestionsUseCase(exerciseModel)
}

createExercise = (params: CreateExerciseArgs) => {
Expand All @@ -16,4 +22,7 @@ export class ExerciseService {
getExercise = (params: GetExerciseArgs) => {
return this.getExercisesUseCase.execute(params)
}
getAutoCompleteSuggestions = (params: GetAutoCompleteSuggestionsArgs) => {
return this.getAutoCompleteSuggestionsUseCase.execute(params)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { IUseCase } from '#common/types/use-case.type.js'
import { IExerciseDoc, IExerciseModel } from '#infra/mongodb/models/exercises/exercise.entity.js'

export interface GetAutoCompleteSuggestionsArgs {
search?: string
}
export class GetAutoCompleteSuggestionsUseCase implements IUseCase<GetAutoCompleteSuggestionsArgs, IExerciseDoc[]> {
constructor(private readonly exerciseModel: IExerciseModel) {}

async execute({ search = '' }: GetAutoCompleteSuggestionsArgs): Promise<IExerciseDoc[]> {
try {
if (!search) {
return []
}

const autocompleteResults = await this.exerciseModel.aggregate([
{
$search: {
index: 'exercises',
autocomplete: {
query: search,
path: 'name',
fuzzy: {
maxEdits: 1,
prefixLength: 1
}
}
}
},
{ $limit: 10 },
{
$project: {
name: 1,
gifUrl: 1,
exerciseId: 1,
score: { $meta: 'searchScore' }
}
},
{ $sort: { score: -1 } }
])

return autocompleteResults
} catch (error) {
throw new Error('Failed to generate exercises suggestions')
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-autocomplete-suggestions.usecase'
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class GetExercisesUseCase implements IUseCase<GetExerciseArgs, GetExercis
exercises: result
}
} catch (error) {
console.error('Error fetching exercises:', error)
throw new Error('Failed to fetch exercises')
}
}
Expand Down

0 comments on commit a08f6d3

Please sign in to comment.