mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-15 02:15:21 +00:00
Merge branch 'dev' of https://github.com/SyncrowIOT/backend into feat/edit-space-model
This commit is contained in:
@ -741,6 +741,10 @@ export class ControllerRoute {
|
||||
|
||||
public static readonly CREATE_USER_INVITATION_DESCRIPTION =
|
||||
'This endpoint creates an invitation for a user to assign to role and spaces.';
|
||||
public static readonly CHECK_EMAIL_SUMMARY = 'Check email';
|
||||
|
||||
public static readonly CHECK_EMAIL_DESCRIPTION =
|
||||
'This endpoint checks if an email already exists and have a project in the system.';
|
||||
};
|
||||
};
|
||||
static PERMISSION = class {
|
||||
|
@ -15,6 +15,7 @@ import { UserEntity } from '../../user/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||
import { InviteUserDto, InviteUserSpaceDto } from '../dtos';
|
||||
import { ProjectEntity } from '../../project/entities';
|
||||
|
||||
@Entity({ name: 'invite-user' })
|
||||
@Unique(['email', 'invitationCode'])
|
||||
@ -91,6 +92,13 @@ export class InviteUserEntity extends AbstractEntity<InviteUserDto> {
|
||||
(inviteUserSpace) => inviteUserSpace.inviteUser,
|
||||
)
|
||||
spaces: InviteUserSpaceEntity[];
|
||||
|
||||
@ManyToOne(() => ProjectEntity, (project) => project.invitedUsers, {
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'project_uuid' })
|
||||
public project: ProjectEntity;
|
||||
|
||||
constructor(partial: Partial<InviteUserEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -34,7 +34,7 @@ export class CommunityEntity extends AbstractEntity<CommunityDto> {
|
||||
externalId: string;
|
||||
|
||||
@ManyToOne(() => ProjectEntity, (project) => project.communities, {
|
||||
nullable: true,
|
||||
nullable: false,
|
||||
})
|
||||
project: ProjectEntity;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { ProjectDto } from '../dtos';
|
||||
import { CommunityEntity } from '../../community/entities';
|
||||
import { SpaceModelEntity } from '../../space-model';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
import { InviteUserEntity } from '../../Invite-user/entities';
|
||||
|
||||
@Entity({ name: 'project' })
|
||||
@Unique(['name'])
|
||||
@ -32,6 +33,9 @@ export class ProjectEntity extends AbstractEntity<ProjectDto> {
|
||||
@OneToMany(() => UserEntity, (user) => user.project)
|
||||
public users: UserEntity[];
|
||||
|
||||
@OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.project)
|
||||
public invitedUsers: InviteUserEntity[];
|
||||
|
||||
constructor(partial: Partial<ProjectEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
|
@ -117,7 +117,7 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
||||
public visitorPasswords: VisitorPasswordEntity[];
|
||||
|
||||
@ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, {
|
||||
nullable: true,
|
||||
nullable: false,
|
||||
})
|
||||
public roleType: RoleTypeEntity;
|
||||
@OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, {
|
||||
|
@ -8,6 +8,7 @@ import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
|
||||
import { UserOtpRepository } from '@app/common/modules/user/repositories';
|
||||
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
||||
import { RoleService } from 'src/role/services';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, UserRepositoryModule, CommonModule],
|
||||
@ -18,6 +19,7 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
||||
UserSessionRepository,
|
||||
UserOtpRepository,
|
||||
RoleTypeRepository,
|
||||
RoleService,
|
||||
],
|
||||
exports: [UserAuthService],
|
||||
})
|
||||
|
@ -18,6 +18,8 @@ import * as argon2 from 'argon2';
|
||||
import { differenceInSeconds } from '@app/common/helper/differenceInSeconds';
|
||||
import { LessThan, MoreThan } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { RoleService } from 'src/role/services';
|
||||
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class UserAuthService {
|
||||
@ -28,6 +30,7 @@ export class UserAuthService {
|
||||
private readonly helperHashService: HelperHashService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly roleService: RoleService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
@ -44,9 +47,13 @@ export class UserAuthService {
|
||||
|
||||
try {
|
||||
const { regionUuid, ...rest } = userSignUpDto;
|
||||
const spaceMemberRole = await this.roleService.findRoleByType(
|
||||
RoleType.SPACE_MEMBER,
|
||||
);
|
||||
const user = await this.userRepository.save({
|
||||
...rest,
|
||||
password: hashedPassword,
|
||||
roleType: { uuid: spaceMemberRole.uuid },
|
||||
region: regionUuid
|
||||
? {
|
||||
uuid: regionUuid,
|
||||
|
@ -6,6 +6,8 @@ import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||
import { Permissions } from 'src/decorators/permissions.decorator';
|
||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||
import { CheckEmailDto } from '../dtos/check-email.dto';
|
||||
|
||||
@ApiTags('Invite User Module')
|
||||
@Controller({
|
||||
@ -34,4 +36,19 @@ export class InviteUserController {
|
||||
user.role.type,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('check-email')
|
||||
@ApiOperation({
|
||||
summary: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_SUMMARY,
|
||||
description: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_DESCRIPTION,
|
||||
})
|
||||
async checkEmailAndProject(
|
||||
@Body() addUserInvitationDto: CheckEmailDto,
|
||||
): Promise<BaseResponseDto> {
|
||||
return await this.inviteUserService.checkEmailAndProject(
|
||||
addUserInvitationDto,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,14 @@ export class AddUserInvitationDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public roleUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'The project uuid of the user',
|
||||
example: 'd290f1ee-6c54-4b01-90e6-d701748f0851',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public projectUuid: string;
|
||||
@ApiProperty({
|
||||
description: 'The array of space UUIDs (at least one required)',
|
||||
example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'],
|
||||
|
16
src/invite-user/dtos/check-email.dto.ts
Normal file
16
src/invite-user/dtos/check-email.dto.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEmail, IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CheckEmailDto {
|
||||
@ApiProperty({
|
||||
description: 'The email of the user',
|
||||
example: 'OqM9A@example.com',
|
||||
required: true,
|
||||
})
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
constructor(dto: Partial<CheckEmailDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
@ -12,11 +12,14 @@ import {
|
||||
InviteUserRepository,
|
||||
InviteUserSpaceRepository,
|
||||
} from '@app/common/modules/Invite-user/repositiories';
|
||||
import { CheckEmailDto } from '../dtos/check-email.dto';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class InviteUserService {
|
||||
constructor(
|
||||
private readonly inviteUserRepository: InviteUserRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly inviteUserSpaceRepository: InviteUserSpaceRepository,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
@ -33,6 +36,7 @@ export class InviteUserService {
|
||||
phoneNumber,
|
||||
roleUuid,
|
||||
spaceUuids,
|
||||
projectUuid,
|
||||
} = dto;
|
||||
|
||||
const invitationCode = generateRandomString(6);
|
||||
@ -67,6 +71,7 @@ export class InviteUserService {
|
||||
status: UserStatusEnum.INVITED,
|
||||
invitationCode,
|
||||
invitedBy: roleType,
|
||||
project: { uuid: projectUuid },
|
||||
});
|
||||
|
||||
const invitedUser = await queryRunner.manager.save(inviteUser);
|
||||
@ -105,4 +110,46 @@ export class InviteUserService {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
async checkEmailAndProject(dto: CheckEmailDto): Promise<BaseResponseDto> {
|
||||
const { email } = dto;
|
||||
|
||||
try {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { email },
|
||||
relations: ['project'],
|
||||
});
|
||||
|
||||
if (user?.project) {
|
||||
throw new HttpException(
|
||||
'This email already has a project',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const invitedUser = await this.inviteUserRepository.findOne({
|
||||
where: { email },
|
||||
relations: ['project'],
|
||||
});
|
||||
|
||||
if (invitedUser?.project) {
|
||||
throw new HttpException(
|
||||
'This email already has a project',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
return new SuccessResponseDto({
|
||||
statusCode: HttpStatus.OK,
|
||||
success: true,
|
||||
message: 'Valid email',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error checking email and project:', error);
|
||||
throw new HttpException(
|
||||
error.message ||
|
||||
'An unexpected error occurred while checking the email',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,15 @@ export class CreateOrphanSpaceHandler
|
||||
async execute(command: CreateOrphanSpaceCommand): Promise<void> {
|
||||
try {
|
||||
const { project } = command;
|
||||
const orphanCommunityName = `${ORPHAN_COMMUNITY_NAME}-${project.name}`;
|
||||
|
||||
let orphanCommunity = await this.communityRepository.findOne({
|
||||
where: { name: ORPHAN_COMMUNITY_NAME, project },
|
||||
where: { name: orphanCommunityName, project },
|
||||
});
|
||||
|
||||
if (!orphanCommunity) {
|
||||
orphanCommunity = this.communityRepository.create({
|
||||
name: ORPHAN_COMMUNITY_NAME,
|
||||
name: orphanCommunityName,
|
||||
description: ORPHAN_COMMUNITY_DESCRIPTION,
|
||||
project,
|
||||
});
|
||||
|
@ -13,4 +13,12 @@ export class RoleService {
|
||||
);
|
||||
return roles;
|
||||
}
|
||||
async findRoleByType(roleType: RoleType) {
|
||||
const role = await this.roleTypeRepository.findOne({
|
||||
where: {
|
||||
type: roleType,
|
||||
},
|
||||
});
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface';
|
||||
import { GetSpaceParam } from '../dtos';
|
||||
@ -25,6 +26,12 @@ export class SpaceDeviceService {
|
||||
spaceUuid,
|
||||
);
|
||||
|
||||
if (space.devices.length === 0) {
|
||||
throw new HttpException(
|
||||
'The space does not contain any devices.',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const safeFetch = async (device: any) => {
|
||||
try {
|
||||
const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya(
|
||||
@ -42,7 +49,7 @@ export class SpaceDeviceService {
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`,
|
||||
`Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error. ${error}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user