Passed
Push — development ( 223a11...811359 )
by Karl
24:36 queued 11:37
created

backend/src/bicycles/bicycles.controller.ts   A

Complexity

Total Complexity 16
Complexity/F 2.29

Size

Lines of Code 241
Function Count 7

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 90.7%

Importance

Changes 0
Metric Value
eloc 199
dl 0
loc 241
ccs 39
cts 43
cp 0.907
rs 10
c 0
b 0
f 0
wmc 16
mnd 9
bc 9
fnc 7
bpm 1.2857
cpm 2.2857
noi 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
A BicyclesController.getBikeById 0 25 1
A BicyclesController.getBicyclesByCity 0 21 1
A BicyclesController.updateBatchPositions 0 21 1
B BicyclesController.getAllBicycles 0 51 8
A BicyclesController.updateBicycle 0 33 1
A BicyclesController.createABike 0 17 1
A BicyclesController.createManyBikes 0 27 3
1 8
import {
2
  Controller,
3
  Get,
4
  Post,
5
  Param,
6
  Patch,
7
  Body,
8
  Query,
9
  BadRequestException,
10
} from '@nestjs/common';
11 8
import {
12
  ApiBearerAuth,
13
  ApiOperation,
14
  ApiResponse,
15
  ApiParam,
16
  ApiBody,
17
  ApiTags,
18
  ApiQuery,
19
} from '@nestjs/swagger';
20
// we have removed all JwtAuthGuards from this route.
21
//  import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
22 8
import { BicyclesService } from './bicycles.service';
23 8
import { UpdateBicycleDto } from './dto/update-bicycle.dto';
24 8
import { Bicycle } from './entities/bicycle.entity';
25
import { BicycleResponse } from './types/bicycle-response.interface';
26 8
import { CreateBicycleDto } from './dto/create-bicycle.dto';
27 8
import { CityName } from 'src/cities/types/city.enum';
28 8
import { BatchUpdateBicyclePositionsDto, BicycleBatchResponseDto } from './dto/batch-update.dto';
29
30 8
const BIKE_ID = 'b1e77dd3-9fb9-4e6c-a5c6-b6fc58f59464';
31 8
const UNAUTHORIZED_ERROR_MESSAGE = 'Unauthorized. Authentication required';
32
33
@ApiTags('Bicycles')
34
@Controller({ path: 'bike', version: '1' })
35 8
export class BicyclesController {
36 9
  constructor(private readonly bicyclesService: BicyclesService) {}
37
38
  @Get()
39
  @ApiBearerAuth()
40
  @ApiOperation({ summary: 'Get all bicycles' })
41
  @ApiQuery({
42
    name: 'city',
43
    required: false,
44
    enum: CityName,
45
  })
46
  @ApiQuery({ name: 'lat', required: false, minimum: -90, maximum: 90 })
47
  @ApiQuery({ name: 'lon', required: false, minimum: -180, maximum: 180 })
48
  @ApiQuery({ name: 'radius', required: false, minimum: 0, maximum: 100000 })
49
  @ApiResponse({
50
    status: 200,
51
    description: 'List of bicycles',
52
    type: [Bicycle],
53
  })
54
  @ApiResponse({
55
    status: 401,
56
    description: 'Unauthorized. Authentication required',
57
  })
58 8
  async getAllBicycles(
59
    @Query('lat') lat?: string,
60
    @Query('lon') lon?: string,
61
    @Query('radius') radius?: string,
62
    @Query('city') city?: CityName,
63
  ): Promise<BicycleResponse[]> {
64 4
    const latitude = lat ? parseFloat(lat) : undefined;
65 4
    const longitude = lon ? parseFloat(lon) : undefined;
66 4
    const radi = radius ? parseFloat(radius) : 3000;
67
68 5
    if ((latitude && !longitude) || (longitude && !latitude)) {
69 1
      throw new BadRequestException('Both lat and lon must be provided for location search');
70
    }
71
72 3
    if (city) {
73 1
      if (latitude) {
74
        return this.bicyclesService.toBicycleResponses(
75
          await this.bicyclesService.findByCityAndLocation(city, latitude, longitude, radi),
76
        );
77
      }
78 1
      return this.bicyclesService.toBicycleResponses(await this.bicyclesService.findByCity(city));
79
    }
80
81 2
    if (latitude) {
82 1
      return this.bicyclesService.toBicycleResponses(
83
        await this.bicyclesService.findByLocation(latitude, longitude, radi),
84
      );
85
    }
86
87 1
    return this.bicyclesService.toBicycleResponses(await this.bicyclesService.findAll());
88
  }
89
90
  @Post('create')
91
  @ApiBearerAuth()
92
  @ApiOperation({ summary: 'Create a new bicycle' })
93
  @ApiBody({
94
    type: CreateBicycleDto,
95
    description: 'Bicycle creation data',
96
    required: false,
97
  })
98
  @ApiResponse({
99
    status: 201,
100
    description: 'Bicycle created successfully',
101
    type: Bicycle,
102
  })
103 8
  async createABike(@Body() createBicycleDto: CreateBicycleDto): Promise<Bicycle> {
104 1
    console.log('skapa cykel');
105 1
    return await this.bicyclesService.createBike(createBicycleDto);
106
  }
107
108
  @Post('create-many')
109
  @ApiBearerAuth()
110
  @ApiOperation({
111
    summary: 'Create multiple bicycles',
112
    description:
113
      'Creates multiple bicycles in a single request. At least one bicycle must be provided.',
114
  })
115
  @ApiBody({
116
    type: [CreateBicycleDto],
117
    description: 'Array of bicycle creation data',
118
    required: true,
119
  })
120
  @ApiResponse({
121
    status: 201,
122
    description: 'Bicycles created successfully',
123
    type: [Bicycle],
124
  })
125
  @ApiResponse({
126
    status: 400,
127
    description: 'Bad Request - Empty array or invalid bicycle data provided',
128
  })
129 8
  async createManyBikes(@Body() createBicycleDto: CreateBicycleDto[]): Promise<Bicycle[]> {
130 1
    if (!createBicycleDto?.length) {
131
      throw new BadRequestException('At least one bike is required');
132
    }
133
    return await this.bicyclesService.createManyBikes(createBicycleDto);
134
  }
135
136
  @Get(':bikeId')
137
  @ApiBearerAuth()
138
  @ApiOperation({ summary: 'Get a bicycle by ID' })
139
  @ApiParam({
140
    name: 'bikeId',
141
    description: 'Unique identifier of the bicycle',
142
    type: 'string',
143
    example: BIKE_ID,
144
  })
145
  @ApiResponse({
146
    status: 200,
147
    description: 'Bicycle details retrieved successfully',
148
    type: Bicycle,
149
  })
150
  @ApiResponse({
151
    status: 401,
152
    description: UNAUTHORIZED_ERROR_MESSAGE,
153
  })
154
  @ApiResponse({
155
    status: 404,
156
    description: 'Bicycle not found',
157
  })
158 8
  async getBikeById(@Param('bikeId') id: string): Promise<Bicycle> {
159 1
    return await this.bicyclesService.findById(id);
160
  }
161
162
  @Patch(':bikeId')
163
  @ApiBearerAuth()
164
  @ApiOperation({ summary: 'Update bicycle by ID' })
165
  @ApiParam({
166
    name: 'bikeId',
167
    description: 'Unique identifier of the bicycle',
168
    type: 'string',
169
    example: BIKE_ID,
170
  })
171
  @ApiBody({
172
    description: 'Bicycle update details',
173
    type: UpdateBicycleDto,
174
  })
175
  @ApiResponse({
176
    status: 200,
177
    description: 'Bicycle updated successfully',
178
    type: Bicycle,
179
  })
180
  @ApiResponse({
181
    status: 400,
182
    description: 'Invalid input',
183
  })
184
  @ApiResponse({
185
    status: 404,
186
    description: 'Bicycle not found',
187
  })
188
  @ApiResponse({
189
    status: 401,
190
    description: UNAUTHORIZED_ERROR_MESSAGE,
191
  })
192 8
  async updateBicycle(@Param('bikeId') bikeId: string, @Body() updateBicycleDto: UpdateBicycleDto) {
193 1
    return this.bicyclesService.update(bikeId, updateBicycleDto);
194
  }
195
196
  @Patch('/batch/positions')
197
  @ApiOperation({ summary: 'Update multiple bicycle positions' })
198
  @ApiResponse({
199
    status: 200,
200
    description: 'Bicycle positions updated successfully',
201
    type: BicycleBatchResponseDto,
202
  })
203
  @ApiResponse({
204
    status: 400,
205
    description: 'Error: Bad Request (Invalid request payload)',
206
  })
207 8
  async updateBatchPositions(
208
    @Body() dto: BatchUpdateBicyclePositionsDto,
209
  ): Promise<BicycleBatchResponseDto> {
210 1
    const results = await this.bicyclesService.updatePositionsParallel(dto.updates);
211 1
    return {
212
      results,
213
      totalCount: results.length,
214 2
      successCount: results.filter((r) => r.success).length,
215 2
      failureCount: results.filter((r) => !r.success).length,
216
    };
217
  }
218
219
  @Get('city/:cityName')
220
  @ApiBearerAuth()
221
  @ApiOperation({ summary: 'Get all bicycles in a specific city' })
222
  @ApiResponse({
223
    status: 200,
224
    description: 'List of bicycles in the specified city',
225
    type: [Bicycle],
226
  })
227
  @ApiResponse({
228
    status: 401,
229
    description: UNAUTHORIZED_ERROR_MESSAGE,
230
  })
231
  @ApiParam({
232
    name: 'cityName',
233
    description: 'Name of the city',
234
    type: 'string',
235
    enum: CityName,
236
  })
237 8
  async getBicyclesByCity(@Param('cityName') cityName: CityName): Promise<Bicycle[]> {
238
    return await this.bicyclesService.findByCity(cityName);
239
  }
240
}
241