Merge pull request #41 from SyncrowIOT/SP-233-be-home-members

feat: Add unit invitation code functionality and user verification us…
This commit is contained in:
faris Aljohari
2024-06-01 20:06:44 +03:00
committed by GitHub
11 changed files with 162 additions and 9 deletions

View File

@ -16,4 +16,8 @@ export class SpaceDto {
@IsString()
@IsNotEmpty()
public spaceTypeUuid: string;
@IsString()
@IsNotEmpty()
public invitationCode: string;
}

View File

@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
import { SpaceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceTypeEntity } from '../../space-type/entities';
@ -6,6 +6,7 @@ import { UserSpaceEntity } from '../../user-space/entities';
import { DeviceEntity } from '../../device/entities';
@Entity({ name: 'space' })
@Unique(['invitationCode'])
export class SpaceEntity extends AbstractEntity<SpaceDto> {
@Column({
type: 'uuid',
@ -18,6 +19,11 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
nullable: false,
})
public spaceName: string;
@Column({
nullable: true,
})
public invitationCode: string;
@ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true })
parent: SpaceEntity;

18
package-lock.json generated
View File

@ -28,6 +28,7 @@
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"morgan": "^1.10.0",
"nanoid": "^5.0.7",
"nodemailer": "^6.9.10",
"passport-jwt": "^4.0.1",
"pg": "^8.11.3",
@ -7165,6 +7166,23 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",

View File

@ -39,6 +39,7 @@
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"morgan": "^1.10.0",
"nanoid": "^5.0.7",
"nodemailer": "^6.9.10",
"passport-jwt": "^4.0.1",
"pg": "^8.11.3",

View File

@ -30,7 +30,7 @@ export class BuildingController {
constructor(private readonly buildingService: BuildingService) {}
@ApiBearerAuth()
@UseGuards(AdminRoleGuard, CheckCommunityTypeGuard)
@UseGuards(JwtAuthGuard, CheckCommunityTypeGuard)
@Post()
async addBuilding(@Body() addBuildingDto: AddBuildingDto) {
try {

View File

@ -32,7 +32,7 @@ export class CommunityController {
constructor(private readonly communityService: CommunityService) {}
@ApiBearerAuth()
@UseGuards(AdminRoleGuard)
@UseGuards(JwtAuthGuard)
@Post()
async addCommunity(@Body() addCommunityDto: AddCommunityDto) {
try {

View File

@ -30,7 +30,7 @@ export class FloorController {
constructor(private readonly floorService: FloorService) {}
@ApiBearerAuth()
@UseGuards(AdminRoleGuard, CheckBuildingTypeGuard)
@UseGuards(JwtAuthGuard, CheckBuildingTypeGuard)
@Post()
async addFloor(@Body() addFloorDto: AddFloorDto) {
try {

View File

@ -28,7 +28,7 @@ export class RoomController {
constructor(private readonly roomService: RoomService) {}
@ApiBearerAuth()
@UseGuards(AdminRoleGuard, CheckUnitTypeGuard)
@UseGuards(JwtAuthGuard, CheckUnitTypeGuard)
@Post()
async addRoom(@Body() addRoomDto: AddRoomDto) {
try {

View File

@ -12,7 +12,11 @@ import {
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { AddUnitDto, AddUserUnitDto } from '../dtos/add.unit.dto';
import {
AddUnitDto,
AddUserUnitDto,
AddUserUnitUsingCodeDto,
} from '../dtos/add.unit.dto';
import { GetUnitChildDto } from '../dtos/get.unit.dto';
import { UpdateUnitNameDto } from '../dtos/update.unit.dto';
import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard';
@ -30,7 +34,7 @@ export class UnitController {
constructor(private readonly unitService: UnitService) {}
@ApiBearerAuth()
@UseGuards(AdminRoleGuard, CheckFloorTypeGuard)
@UseGuards(JwtAuthGuard, CheckFloorTypeGuard)
@Post()
async addUnit(@Body() addUnitDto: AddUnitDto) {
try {
@ -147,4 +151,39 @@ export class UnitController {
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, UnitPermissionGuard)
@Get(':unitUuid/invitation-code')
async getUnitInvitationCode(@Param('unitUuid') unitUuid: string) {
try {
const unit = await this.unitService.getUnitInvitationCode(unitUuid);
return unit;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('user/verify-code')
async verifyCodeAndAddUserUnit(
@Body() addUserUnitUsingCodeDto: AddUserUnitUsingCodeDto,
) {
try {
await this.unitService.verifyCodeAndAddUserUnit(addUserUnitUsingCodeDto);
return {
statusCode: HttpStatus.CREATED,
success: true,
message: 'user unit added successfully',
};
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -40,3 +40,29 @@ export class AddUserUnitDto {
Object.assign(this, dto);
}
}
export class AddUserUnitUsingCodeDto {
@ApiProperty({
description: 'unitUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public unitUuid: string;
@ApiProperty({
description: 'userUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public userUuid: string;
@ApiProperty({
description: 'inviteCode',
required: true,
})
@IsString()
@IsNotEmpty()
public inviteCode: string;
constructor(dto: Partial<AddUserUnitDto>) {
Object.assign(this, dto);
}
}

View File

@ -7,7 +7,7 @@ import {
BadRequestException,
} from '@nestjs/common';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { AddUnitDto, AddUserUnitDto } from '../dtos';
import { AddUnitDto, AddUserUnitDto, AddUserUnitUsingCodeDto } from '../dtos';
import {
UnitChildInterface,
UnitParentInterface,
@ -227,7 +227,7 @@ export class UnitService {
async addUserUnit(addUserUnitDto: AddUserUnitDto) {
try {
await this.userSpaceRepository.save({
return await this.userSpaceRepository.save({
user: { uuid: addUserUnitDto.userUuid },
space: { uuid: addUserUnitDto.unitUuid },
});
@ -282,4 +282,63 @@ export class UnitService {
}
}
}
async getUnitInvitationCode(unitUuid: string): Promise<any> {
try {
const { nanoid } = await import('nanoid');
// Generate a 6-character random invitation code
const invitationCode = nanoid(6);
// Update the unit with the new invitation code
await this.spaceRepository.update({ uuid: unitUuid }, { invitationCode });
// Fetch the updated unit
const updatedUnit = await this.spaceRepository.findOneOrFail({
where: { uuid: unitUuid },
relations: ['spaceType'],
});
return {
uuid: updatedUnit.uuid,
invitationCode: updatedUnit.invitationCode,
type: updatedUnit.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err;
} else {
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
}
}
}
async verifyCodeAndAddUserUnit(
addUserUnitUsingCodeDto: AddUserUnitUsingCodeDto,
) {
try {
const unit = await this.spaceRepository.findOneOrFail({
where: {
invitationCode: addUserUnitUsingCodeDto.inviteCode,
spaceType: { type: 'unit' },
},
relations: ['spaceType'],
});
if (unit.invitationCode) {
const user = await this.addUserUnit({
userUuid: addUserUnitUsingCodeDto.userUuid,
unitUuid: unit.uuid,
});
if (user.uuid) {
await this.spaceRepository.update(
{ uuid: unit.uuid },
{ invitationCode: null },
);
}
}
} catch (err) {
throw new HttpException(
'Invalid invitation code',
HttpStatus.BAD_REQUEST,
);
}
}
}