From e79e09065c663d3c040b1fcc8e0b6070059c26ca Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Thu, 12 Jan 2023 20:53:24 +0200 Subject: [PATCH] room apis --- src/app-building/app-building.controller.ts | 103 +++++++++++++++- src/app-building/app-building.service.ts | 112 +++++++++++++++++- .../dto/buildings-create-room-request.dto.ts | 18 +++ ....dto.ts => buildings-floor-request.dto.ts} | 0 .../dto/buildings-response.dto.ts | 17 ++- .../dto/buildings-update-request.dto.ts | 6 + .../dto/buildings-update-room-request.dto.ts | 0 src/app.controller.ts | 2 + src/objects/building/building.service.ts | 68 +++++++++++ src/objects/building/entities/room.entity.ts | 8 ++ 10 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 src/app-building/dto/buildings-create-room-request.dto.ts rename src/app-building/dto/{building-floor-request.dto.ts => buildings-floor-request.dto.ts} (100%) create mode 100644 src/app-building/dto/buildings-update-request.dto.ts create mode 100644 src/app-building/dto/buildings-update-room-request.dto.ts diff --git a/src/app-building/app-building.controller.ts b/src/app-building/app-building.controller.ts index 80dd1d7..5024849 100644 --- a/src/app-building/app-building.controller.ts +++ b/src/app-building/app-building.controller.ts @@ -17,18 +17,26 @@ import { ApiOkResponse, ApiBody, ApiNotFoundResponse, + ApiParam, } from '@nestjs/swagger'; import { User } from 'src/objects/user/user.entity'; import { LoggedInUser } from 'src/shared/decorators/user.decorator'; import { AuthGuard } from 'src/shared/guards/auth.guard'; import { AppBuildingService } from './app-building.service'; -import { BuildingFloorUpdateRequestDto } from './dto/building-floor-request.dto'; +import { BuildingFloorUpdateRequestDto } from './dto/buildings-floor-request.dto'; import { CreateBuildingRequestDto } from './dto/buildings-create-request.dto'; import { BuildingFloorResponseDto, + BuildingRoomResponseDto, + BuildingRoomWithFloorResponseDto, BuildingsListResponseDto, BuildingsResponseDto, } from './dto/buildings-response.dto'; +import { BuildingsUpdateRequestDto } from './dto/buildings-update-request.dto'; +import { + BuildingsCreateRoomRequestDto, + BuildingsUpdateRoomRequestDto, +} from './dto/buildings-create-room-request.dto'; @Controller({ path: 'buildings', @@ -62,6 +70,7 @@ export class AppBuildingController { } @Get(':id') + @ApiParam({ name: 'id', description: 'Building ID' }) @ApiOperation({ summary: 'Get building by ID' }) @ApiNotFoundResponse({ description: 'Building not found' }) @ApiOkResponse({ type: BuildingsResponseDto }) @@ -72,8 +81,23 @@ export class AppBuildingController { return this.service.getBuildingById(user, id); } + @Patch(':id') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiOperation({ summary: 'Update building by ID' }) + @ApiBody({ type: BuildingsUpdateRequestDto }) + @ApiNotFoundResponse({ description: 'Building not found' }) + @ApiOkResponse({ type: BuildingsListResponseDto }) + async updateBuildingById( + @Param('id', ParseIntPipe) id: number, + @Body() body: BuildingsUpdateRequestDto, + @LoggedInUser() user: User, + ): Promise { + return this.service.updateBuilding(user, id, body); + } + @Get(':id/floors') - @ApiOperation({ summary: 'Get building floors by ID' }) + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiOperation({ summary: 'Get building floors' }) @ApiOkResponse({ type: BuildingFloorResponseDto, isArray: true }) async getBuildingFloorsById( @Param('id', ParseIntPipe) id: number, @@ -83,7 +107,9 @@ export class AppBuildingController { } @Get(':id/floors/:number') - @ApiOperation({ summary: 'Get building floors by ID' }) + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'number', description: 'Floor number' }) + @ApiOperation({ summary: 'Get building floor by floor number' }) @ApiOkResponse({ type: BuildingFloorResponseDto }) async getBuildingFloorByIdByNumber( @Param('id', ParseIntPipe) id: number, @@ -94,8 +120,10 @@ export class AppBuildingController { } @Patch(':id/floors/:number') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'number', description: 'Floor number' }) @ApiBody({ type: BuildingFloorUpdateRequestDto }) - @ApiOperation({ summary: 'Update building floor' }) + @ApiOperation({ summary: 'Update building floor by floor number' }) @ApiOkResponse({ type: BuildingFloorResponseDto }) async updateBuildingFloorByIdByNumber( @Param('id', ParseIntPipe) id: number, @@ -105,4 +133,71 @@ export class AppBuildingController { ): Promise { return this.service.updateBuildingFloor(user, body, id, floor); } + + @Post(':id/floors/:number/rooms') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'number', description: 'Floor number' }) + @ApiBody({ type: BuildingsCreateRoomRequestDto }) + @ApiOperation({ summary: 'Create new room on floor' }) + @ApiOkResponse({ type: BuildingRoomResponseDto }) + async createNewRoom( + @Param('id', ParseIntPipe) id: number, + @Param('number', ParseIntPipe) floor: number, + @Body() body: BuildingsCreateRoomRequestDto, + @LoggedInUser() user: User, + ): Promise { + return this.service.addNewRoom(user, body, id, floor); + } + + @Get(':id/floors/:number/rooms') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'number', description: 'Floor number' }) + @ApiOperation({ summary: 'Get rooms on floor' }) + @ApiOkResponse({ type: BuildingRoomResponseDto, isArray: true }) + async getRoomsOnFloor( + @Param('id', ParseIntPipe) id: number, + @Param('number', ParseIntPipe) floor: number, + @LoggedInUser() user: User, + ): Promise { + return this.service.getRoomsOnFloor(user, id, floor); + } + + @Get(':id/rooms') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiOperation({ summary: 'Get rooms in building' }) + @ApiOkResponse({ type: BuildingRoomWithFloorResponseDto, isArray: true }) + async getRooms( + @Param('id', ParseIntPipe) id: number, + @LoggedInUser() user: User, + ): Promise { + return this.service.getRoomsInBuilding(user, id); + } + + @Get(':id/rooms/:roomId') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'roomId', description: 'Room ID' }) + @ApiOperation({ summary: 'Get room by ID' }) + @ApiOkResponse({ type: BuildingRoomResponseDto }) + async getRoomById( + @Param('id', ParseIntPipe) id: number, + @Param('roomId', ParseIntPipe) roomId: number, + @LoggedInUser() user: User, + ): Promise { + return this.service.getRoom(user, id, roomId); + } + + @Patch(':id/rooms/:roomId') + @ApiParam({ name: 'id', description: 'Building ID' }) + @ApiParam({ name: 'roomId', description: 'Room ID' }) + @ApiBody({ type: BuildingsUpdateRoomRequestDto }) + @ApiOperation({ summary: 'Update room by ID' }) + @ApiOkResponse({ type: BuildingRoomResponseDto }) + async updateRoomById( + @Param('id', ParseIntPipe) id: number, + @Param('roomId', ParseIntPipe) roomId: number, + @Body() body: BuildingsUpdateRoomRequestDto, + @LoggedInUser() user: User, + ): Promise { + return this.service.updateRoom(user, body, id, roomId); + } } diff --git a/src/app-building/app-building.service.ts b/src/app-building/app-building.service.ts index bc7dae3..8cef9e5 100644 --- a/src/app-building/app-building.service.ts +++ b/src/app-building/app-building.service.ts @@ -5,8 +5,13 @@ import { BuildingService } from 'src/objects/building/building.service'; import { GroupService } from 'src/objects/group/group.service'; import { User } from 'src/objects/user/user.entity'; import { UserService } from 'src/objects/user/user.service'; -import { BuildingFloorUpdateRequestDto } from './dto/building-floor-request.dto'; +import { BuildingFloorUpdateRequestDto } from './dto/buildings-floor-request.dto'; import { CreateBuildingRequestDto } from './dto/buildings-create-request.dto'; +import { BuildingsUpdateRequestDto } from './dto/buildings-update-request.dto'; +import { + BuildingsCreateRoomRequestDto, + BuildingsUpdateRoomRequestDto, +} from './dto/buildings-create-room-request.dto'; @Injectable() export class AppBuildingService { @@ -49,6 +54,27 @@ export class AppBuildingService { return omit(newBuilding, ['groups']); } + async updateBuilding( + user: User, + id: number, + body: BuildingsUpdateRequestDto, + ) { + const building = await this.buildingService.getBuildingByIdAndUserSub( + id, + user.sub, + ); + + if (!building) { + throw new NotFoundException('Building not found'); + } + + Object.assign(building, body); + + await this.buildingService.saveBuilding(building); + + return building; + } + async getBuildingById(user: User, id: number) { const building = await this.buildingService.getBuildingByIdAndUserSub( id, @@ -110,4 +136,88 @@ export class AppBuildingService { await this.buildingService.saveFloor(floor); return omit(floor, ['building']); } + + async addNewRoom( + user: User, + body: BuildingsCreateRoomRequestDto, + buildingId: number, + floorNumber: number, + ) { + const floor = await this.buildingService.getFloorByBuildingAndUserSub( + buildingId, + floorNumber, + user.sub, + ['building', 'rooms'], + ); + + if (!floor) { + throw new NotFoundException('Floor not found'); + } + + const newRoom = await this.buildingService.saveRoom({ + ...body, + floor, + building: floor.building, + }); + + floor.rooms = [...floor.rooms, newRoom]; + + return omit(newRoom, ['building', 'floor']); + } + + async getRoomsOnFloor(user: User, buildingId: number, floorNumber: number) { + return this.buildingService.getRoomsOnFloorByBuildingAndUserSub( + buildingId, + floorNumber, + user.sub, + ); + } + + async getRoomsInBuilding(user: User, buildingId: number) { + const rooms = await this.buildingService.getRoomsByBuildingAndUserSub( + buildingId, + user.sub, + ['floor'], + ); + + return rooms.map((room) => ({ + ...room, + floor: pick(room.floor, ['id', 'displayName', 'number']), + })); + } + + async getRoom(user: User, buildingId: number, roomId: number) { + const room = await this.buildingService.getRoomByBuildingAndUserSub( + buildingId, + roomId, + user.sub, + ); + if (!room) { + throw new NotFoundException('Room not found'); + } + return room; + } + + async updateRoom( + user: User, + body: BuildingsUpdateRoomRequestDto, + buildingId: number, + roomId: number, + ) { + const room = await this.buildingService.getRoomByBuildingAndUserSub( + buildingId, + roomId, + user.sub, + ); + + if (!room) { + throw new NotFoundException('Room not found'); + } + + Object.assign(room, body); + + await this.buildingService.saveRoom(room); + + return room; + } } diff --git a/src/app-building/dto/buildings-create-room-request.dto.ts b/src/app-building/dto/buildings-create-room-request.dto.ts new file mode 100644 index 0000000..5d3971b --- /dev/null +++ b/src/app-building/dto/buildings-create-room-request.dto.ts @@ -0,0 +1,18 @@ +import { PartialType, PickType } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +import { Room } from 'src/objects/building/entities/room.entity'; + +export class BuildingsCreateRoomRequestDto extends PickType(Room, [ + 'displayName', + 'plan', +]) { + @IsString() + displayName: string; + + @IsString() + plan: string; +} + +export class BuildingsUpdateRoomRequestDto extends PartialType( + BuildingsCreateRoomRequestDto, +) {} diff --git a/src/app-building/dto/building-floor-request.dto.ts b/src/app-building/dto/buildings-floor-request.dto.ts similarity index 100% rename from src/app-building/dto/building-floor-request.dto.ts rename to src/app-building/dto/buildings-floor-request.dto.ts diff --git a/src/app-building/dto/buildings-response.dto.ts b/src/app-building/dto/buildings-response.dto.ts index ed12c42..6e6cf14 100644 --- a/src/app-building/dto/buildings-response.dto.ts +++ b/src/app-building/dto/buildings-response.dto.ts @@ -1,9 +1,24 @@ -import { ApiProperty, OmitType } from '@nestjs/swagger'; +import { ApiProperty, OmitType, PickType } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { Building } from 'src/objects/building/entities/building.entity'; import { Floor } from 'src/objects/building/entities/floor.entity'; import { Room } from 'src/objects/building/entities/room.entity'; +export class BuildingMinimalFloorResponseDto extends PickType(Floor, [ + 'id', + 'number', + 'displayName', +]) {} + +export class BuildingRoomWithFloorResponseDto extends OmitType(Room, [ + 'floor', + 'building', +]) { + @ApiProperty({ type: BuildingMinimalFloorResponseDto }) + @Type(() => BuildingMinimalFloorResponseDto) + floor: BuildingMinimalFloorResponseDto; +} + export class BuildingRoomResponseDto extends OmitType(Room, [ 'floor', 'building', diff --git a/src/app-building/dto/buildings-update-request.dto.ts b/src/app-building/dto/buildings-update-request.dto.ts new file mode 100644 index 0000000..95c6355 --- /dev/null +++ b/src/app-building/dto/buildings-update-request.dto.ts @@ -0,0 +1,6 @@ +import { PartialType, PickType } from '@nestjs/swagger'; +import { Building } from 'src/objects/building/entities/building.entity'; + +export class BuildingsUpdateRequestDto extends PartialType( + PickType(Building, ['address', 'color', 'displayName']), +) {} diff --git a/src/app-building/dto/buildings-update-room-request.dto.ts b/src/app-building/dto/buildings-update-room-request.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app.controller.ts b/src/app.controller.ts index 16717a5..db1e0e3 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,4 +1,5 @@ import { Controller, Get, Inject } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; import { AppService } from './app.service'; @Controller() @@ -15,6 +16,7 @@ export class AppController { } @Get('/.well-known/jwks.json') + @ApiOperation({ summary: 'JWT public key in JWK format' }) getJWKs() { return { keys: [this.jwks], diff --git a/src/objects/building/building.service.ts b/src/objects/building/building.service.ts index ed19a83..65ee0a2 100644 --- a/src/objects/building/building.service.ts +++ b/src/objects/building/building.service.ts @@ -67,6 +67,74 @@ export class BuildingService { }); } + async getRoomByBuildingAndUserSub( + buildingId: number, + roomId: number, + sub: string, + relations = [], + ) { + return this.roomRepository.findOne({ + where: { + id: roomId, + building: { + id: buildingId, + groups: { + members: { + sub, + }, + }, + }, + }, + relations, + }); + } + + async getRoomsOnFloorByBuildingAndUserSub( + buildingId: number, + floorNumber: number, + sub: string, + relations = [], + ) { + return this.roomRepository.find({ + where: { + floor: { + number: floorNumber, + building: { + id: buildingId, + groups: { + members: { + sub, + }, + }, + }, + }, + }, + relations, + }); + } + + async getRoomsByBuildingAndUserSub( + buildingId: number, + sub: string, + relations = [], + ) { + return this.roomRepository.find({ + where: { + floor: { + building: { + id: buildingId, + groups: { + members: { + sub, + }, + }, + }, + }, + }, + relations, + }); + } + async getFloorsByBuildingAndUserSub( buildingId: number, sub: string, diff --git a/src/objects/building/entities/room.entity.ts b/src/objects/building/entities/room.entity.ts index cb8c1b1..80b8c04 100644 --- a/src/objects/building/entities/room.entity.ts +++ b/src/objects/building/entities/room.entity.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { Column, CreateDateColumn, @@ -11,24 +12,31 @@ import { Floor } from './floor.entity'; @Entity() export class Room { + @ApiProperty() @PrimaryGeneratedColumn() id: number; + @ApiProperty({ type: () => Building }) @ManyToOne(() => Building) building: Building; + @ApiProperty({ type: () => Floor }) @ManyToOne(() => Floor) floor: Floor; + @ApiProperty() @Column() displayName: string; + @ApiProperty() @Column() plan: string; + @ApiProperty() @CreateDateColumn() createdAt: Date; + @ApiProperty() @UpdateDateColumn() updatedAt: Date; }