Skip to content

Commit b3c8a9c

Browse files
committed
feat(muscles): added route endpoint to get exercises by specific muscle
1 parent d5786f2 commit b3c8a9c

File tree

4 files changed

+119
-4
lines changed

4 files changed

+119
-4
lines changed

src/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export class App {
9797
description:
9898
'Access detailed data on over 1300+ exercises with the ExerciseDB API. This API offers extensive information on each exercise, including target body parts, equipment needed, GIFs for visual guidance, and step-by-step instructions.'
9999
},
100+
100101
spec: {
101102
url: '/swagger'
102103
}

src/modules/images/controllers/image.controller.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,19 @@ export class ImagesController implements Routes {
8787
}
8888
}),
8989
async (ctx) => {
90+
const { origin, pathname } = new URL(ctx.req.url)
9091
const body = await ctx.req.parseBody()
9192
const file = body['file'] as File
9293
if (!file || file.type !== 'image/gif') {
9394
return ctx.json({ success: false, error: 'Invalid file type. Only GIF files are allowed.' }, 400)
9495
}
9596

96-
const uploadResult = await this.imageService.uploadImage(file)
97-
return ctx.json({ success: true, data: uploadResult })
97+
const { publicUrl } = await this.imageService.uploadImage(file)
98+
99+
return ctx.json({
100+
success: true,
101+
data: { publicUrl: `${origin}/api/v1/Images/${publicUrl.split('/').slice(-1)}` }
102+
})
98103
}
99104
)
100105
this.controller.openapi(

src/modules/muscles/controllers/muscle.controller.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { MuscleModel } from '../models/muscle.model'
55
import { MuscleService } from '../services'
66
import Muscle from '#infra/mongodb/models/muscles/muscle.schema.js'
77
import { HTTPException } from 'hono/http-exception'
8+
import Exercise from '#infra/mongodb/models/exercises/exercise.schema.js'
9+
import { ExerciseModel } from '#modules/exercises/models/exercise.model.js'
810

911
export class MuscleController implements Routes {
1012
public controller: OpenAPIHono
1113
private readonly muscleService: MuscleService
1214
constructor() {
1315
this.controller = new OpenAPIHono()
14-
this.muscleService = new MuscleService(Muscle)
16+
this.muscleService = new MuscleService(Muscle, Exercise)
1517
}
1618

1719
public initRoutes() {
@@ -129,5 +131,88 @@ export class MuscleController implements Routes {
129131
return ctx.json({ success: true, data: response })
130132
}
131133
)
134+
135+
this.controller.openapi(
136+
createRoute({
137+
method: 'get',
138+
path: '/muscles/{muscleName}/exercises',
139+
tags: ['Muscles'],
140+
summary: 'Retrive exercises by muscles',
141+
description: 'Retrive list of exercises by targetMuscles or secondaryMuscles.',
142+
operationId: 'getExercisesByEquipment',
143+
request: {
144+
params: z.object({
145+
muscleName: z.string().openapi({
146+
description: 'muscles name',
147+
type: 'string',
148+
example: 'upper back',
149+
default: 'upper back'
150+
})
151+
}),
152+
query: z.object({
153+
offset: z.coerce.number().nonnegative().optional().openapi({
154+
title: 'Offset',
155+
description:
156+
'The number of exercises to skip from the start of the list. Useful for pagination to fetch subsequent pages of results.',
157+
type: 'number',
158+
example: 10,
159+
default: 0
160+
}),
161+
limit: z.coerce.number().positive().max(100).optional().openapi({
162+
title: 'Limit',
163+
description:
164+
'The maximum number of exercises to return in the response. Limits the number of results for pagination purposes.',
165+
maximum: 100,
166+
minimum: 1,
167+
type: 'number',
168+
example: 10,
169+
default: 10
170+
})
171+
})
172+
},
173+
responses: {
174+
200: {
175+
description: 'Successful response with list of all exercises.',
176+
content: {
177+
'application/json': {
178+
schema: z.object({
179+
success: z.boolean().openapi({
180+
description: 'Indicates whether the request was successful',
181+
type: 'boolean',
182+
example: true
183+
}),
184+
data: z.array(ExerciseModel).openapi({
185+
description: 'Array of Exercises.'
186+
})
187+
})
188+
}
189+
}
190+
},
191+
500: {
192+
description: 'Internal server error'
193+
}
194+
}
195+
}),
196+
async (ctx) => {
197+
const { offset, limit = 10 } = ctx.req.valid('query')
198+
const search = ctx.req.param('muscleName')
199+
const { origin, pathname } = new URL(ctx.req.url)
200+
const response = await this.muscleService.getExercisesByMuscles({ offset, limit, search })
201+
return ctx.json({
202+
success: true,
203+
data: {
204+
previousPage:
205+
response.currentPage > 1
206+
? `${origin}${pathname}?offset=${(response.currentPage - 1) * limit}&limit=${limit}`
207+
: null,
208+
nextPage:
209+
response.currentPage < response.totalPages
210+
? `${origin}${pathname}?offset=${response.currentPage * limit}&limit=${limit}`
211+
: null,
212+
...response
213+
}
214+
})
215+
}
216+
)
132217
}
133218
}

src/modules/muscles/services/muscle.service.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1+
import { IExerciseModel } from '#infra/mongodb/models/exercises/exercise.entity.js'
12
import { IMuscleModel } from '#infra/mongodb/models/muscles/muscle.entity.js'
3+
import { GetExerciseSerivceArgs } from '#modules/exercises/services/exercise.service.js'
4+
import {
5+
GetExercisesArgs,
6+
GetExercisesUseCase
7+
} from '#modules/exercises/use-cases/get-exercises/get-exercise.usecase.js'
28
import { CreateMuscleArgs, CreateMuscleUseCase } from '../use-cases/create-muscle'
39
import { GetMusclesUseCase } from '../use-cases/get-muscles'
410

511
export class MuscleService {
612
private readonly createMuscleUseCase: CreateMuscleUseCase
713
private readonly getMuscleUseCase: GetMusclesUseCase
14+
private readonly getExercisesUseCase: GetExercisesUseCase
815

9-
constructor(private readonly muscleModel: IMuscleModel) {
16+
constructor(
17+
private readonly muscleModel: IMuscleModel,
18+
private readonly exerciseModel: IExerciseModel
19+
) {
1020
this.createMuscleUseCase = new CreateMuscleUseCase(muscleModel)
1121
this.getMuscleUseCase = new GetMusclesUseCase(muscleModel)
22+
this.getExercisesUseCase = new GetExercisesUseCase(exerciseModel)
1223
}
1324

1425
createMuscle = (args: CreateMuscleArgs) => {
@@ -18,4 +29,17 @@ export class MuscleService {
1829
getMuscles = () => {
1930
return this.getMuscleUseCase.execute()
2031
}
32+
getExercisesByMuscles = (params: GetExerciseSerivceArgs) => {
33+
const query: GetExercisesArgs = {
34+
offset: params.offset,
35+
limit: params.limit,
36+
query: {
37+
targetMuscles: {
38+
$all: [params.search]
39+
}
40+
}
41+
}
42+
43+
return this.getExercisesUseCase.execute(query)
44+
}
2145
}

0 commit comments

Comments
 (0)