mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-03-10 20:41:44 +00:00
feat: add user profile picture, region, and timezone update functionality
This commit is contained in:
45
src/guards/profile.picture.guard.ts
Normal file
45
src/guards/profile.picture.guard.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class CheckProfilePictureGuard implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
try {
|
||||
if (req.body) {
|
||||
const { profilePicture } = req.body;
|
||||
if (profilePicture) {
|
||||
const isBase64 = /^data:image\/[a-z]+;base64,/.test(profilePicture);
|
||||
if (!isBase64) {
|
||||
throw new BadRequestException(
|
||||
'Profile picture must be in base64 format.',
|
||||
);
|
||||
}
|
||||
|
||||
// Get the size of the base64 string (in bytes)
|
||||
const base64StringLength =
|
||||
profilePicture.length - 'data:image/[a-z]+;base64,'.length;
|
||||
const base64ImageSizeInBytes = base64StringLength * 0.75; // Base64 encoding expands data by 33%
|
||||
const maxSizeInBytes = 1 * 1024 * 1024; // 1 MB
|
||||
|
||||
// Check if the size exceeds the limit
|
||||
if (base64ImageSizeInBytes > maxSizeInBytes) {
|
||||
throw new BadRequestException(
|
||||
'Profile picture size exceeds the allowed limit.',
|
||||
);
|
||||
}
|
||||
}
|
||||
// Check if profilePicture is a base64 string
|
||||
} else {
|
||||
throw new BadRequestException('Invalid request parameters');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('Profile picture guard error: ', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,22 @@
|
||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { UserListDto } from '../dtos/user.list.dto';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
||||
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
|
||||
import {
|
||||
UpdateProfilePictureDataDto,
|
||||
UpdateRegionDataDto,
|
||||
UpdateTimezoneDataDto,
|
||||
} from '../dtos';
|
||||
import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard';
|
||||
|
||||
@ApiTags('User Module')
|
||||
@Controller({
|
||||
@ -13,13 +27,91 @@ export class UserController {
|
||||
constructor(private readonly userService: UserService) {}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminRoleGuard)
|
||||
@Get('list')
|
||||
async userList(@Query() userListDto: UserListDto) {
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get(':userUuid')
|
||||
async getUserDetailsByUserUuid(@Param('userUuid') userUuid: string) {
|
||||
try {
|
||||
return await this.userService.userDetails(userListDto);
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
return await this.userService.getUserDetailsByUserUuid(userUuid);
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, CheckProfilePictureGuard)
|
||||
@Put('/profile-picture/:userUuid')
|
||||
async updateProfilePictureByUserUuid(
|
||||
@Param('userUuid') userUuid: string,
|
||||
@Body() updateProfilePictureDataDto: UpdateProfilePictureDataDto,
|
||||
) {
|
||||
try {
|
||||
const userData = await this.userService.updateProfilePictureByUserUuid(
|
||||
userUuid,
|
||||
updateProfilePictureDataDto,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'profile picture updated successfully',
|
||||
data: userData,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('/region/:userUuid')
|
||||
async updateRegionByUserUuid(
|
||||
@Param('userUuid') userUuid: string,
|
||||
@Body() updateRegionDataDto: UpdateRegionDataDto,
|
||||
) {
|
||||
try {
|
||||
const userData = await this.userService.updateRegionByUserUuid(
|
||||
userUuid,
|
||||
updateRegionDataDto,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'region updated successfully',
|
||||
data: userData,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Put('/timezone/:userUuid')
|
||||
async updateTimezoneByUserUuid(
|
||||
@Param('userUuid') userUuid: string,
|
||||
@Body() updateTimezoneDataDto: UpdateTimezoneDataDto,
|
||||
) {
|
||||
try {
|
||||
const userData = await this.userService.updateTimezoneByUserUuid(
|
||||
userUuid,
|
||||
updateTimezoneDataDto,
|
||||
);
|
||||
return {
|
||||
statusCode: HttpStatus.CREATED,
|
||||
success: true,
|
||||
message: 'timezone updated successfully',
|
||||
data: userData,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export * from './user.list.dto';
|
||||
export * from './update.user.dto';
|
||||
|
||||
42
src/users/dtos/update.user.dto.ts
Normal file
42
src/users/dtos/update.user.dto.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdateProfilePictureDataDto {
|
||||
@ApiProperty({
|
||||
description: 'profilePicture',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public profilePicture: string;
|
||||
|
||||
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class UpdateRegionDataDto {
|
||||
@ApiProperty({
|
||||
description: 'regionUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public regionUuid: string;
|
||||
|
||||
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
export class UpdateTimezoneDataDto {
|
||||
@ApiProperty({
|
||||
description: 'timezoneUuid',
|
||||
required: true,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public timezoneUuid: string;
|
||||
|
||||
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||
Object.assign(this, dto);
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumberString,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from 'class-validator';
|
||||
|
||||
export class UserListDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
schema: string;
|
||||
|
||||
@IsNumberString()
|
||||
@IsNotEmpty()
|
||||
page_no: number;
|
||||
|
||||
@IsNumberString()
|
||||
@IsNotEmpty()
|
||||
page_size: number;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
username: string;
|
||||
|
||||
@IsNumberString()
|
||||
@IsOptional()
|
||||
start_time: number;
|
||||
|
||||
@IsNumberString()
|
||||
@IsOptional()
|
||||
end_time: number;
|
||||
}
|
||||
@ -1,28 +1,185 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||
import { UserListDto } from '../dtos/user.list.dto';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
UpdateProfilePictureDataDto,
|
||||
UpdateRegionDataDto,
|
||||
UpdateTimezoneDataDto,
|
||||
} from './../dtos/update.user.dto';
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
private tuya: TuyaContext;
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||
const tuyaEuUrl = this.configService.get<string>('tuya-config.TUYA_EU_URL');
|
||||
this.tuya = new TuyaContext({
|
||||
baseUrl: tuyaEuUrl,
|
||||
accessKey,
|
||||
secretKey,
|
||||
});
|
||||
constructor(
|
||||
private readonly userRepository: UserRepository,
|
||||
private readonly regionRepository: RegionRepository,
|
||||
private readonly timeZoneRepository: TimeZoneRepository,
|
||||
) {}
|
||||
async getUserDetailsByUserUuid(userUuid: string) {
|
||||
try {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
uuid: userUuid,
|
||||
},
|
||||
relations: ['region', 'timezone'],
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException('Invalid room UUID');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: user.uuid,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
profilePicture: user.profilePicture,
|
||||
region: user.region,
|
||||
timeZone: user.timezone,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async userDetails(userListDto: UserListDto) {
|
||||
const path = `/v2.0/apps/${userListDto.schema}/users`;
|
||||
const data = await this.tuya.request({
|
||||
method: 'GET',
|
||||
path,
|
||||
query: userListDto,
|
||||
});
|
||||
return data;
|
||||
|
||||
async updateProfilePictureByUserUuid(
|
||||
userUuid: string,
|
||||
updateProfilePictureDataDto: UpdateProfilePictureDataDto,
|
||||
) {
|
||||
try {
|
||||
await this.userRepository.update(
|
||||
{ uuid: userUuid },
|
||||
{ ...updateProfilePictureDataDto },
|
||||
);
|
||||
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||
return {
|
||||
uuid: updatedUser.uuid,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
profilePicture: updatedUser.profilePicture,
|
||||
region: updatedUser.region,
|
||||
timeZoneUuid: updatedUser.timeZone,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
|
||||
if (err instanceof BadRequestException) {
|
||||
throw err; // Re-throw BadRequestException
|
||||
} else {
|
||||
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
async updateRegionByUserUuid(
|
||||
userUuid: string,
|
||||
updateRegionDataDto: UpdateRegionDataDto,
|
||||
) {
|
||||
try {
|
||||
const user = await this.getUserDetailsByUserUuid(userUuid);
|
||||
if (!user) {
|
||||
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Ensure the region UUID is provided
|
||||
if (!updateRegionDataDto.regionUuid) {
|
||||
throw new BadRequestException('Region UUID is required');
|
||||
}
|
||||
|
||||
// Ensure the region exists
|
||||
const region = await this.regionRepository.findOne({
|
||||
where: {
|
||||
uuid: updateRegionDataDto.regionUuid,
|
||||
},
|
||||
});
|
||||
if (!region) {
|
||||
throw new BadRequestException('Invalid region UUID');
|
||||
}
|
||||
|
||||
await this.userRepository.update(
|
||||
{ uuid: userUuid },
|
||||
{
|
||||
region: region,
|
||||
},
|
||||
);
|
||||
|
||||
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||
if (!updatedUser.region) {
|
||||
throw new BadRequestException('Region update failed');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: updatedUser.uuid,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
profilePicture: updatedUser.profilePicture,
|
||||
region: updatedUser.region,
|
||||
timeZoneUuid: updatedUser.timeZone,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new HttpException(
|
||||
err.message || 'User not found',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
async updateTimezoneByUserUuid(
|
||||
userUuid: string,
|
||||
updateTimezoneDataDto: UpdateTimezoneDataDto,
|
||||
) {
|
||||
try {
|
||||
const user = await this.getUserDetailsByUserUuid(userUuid);
|
||||
if (!user) {
|
||||
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Ensure the region UUID is provided
|
||||
if (!updateTimezoneDataDto.timezoneUuid) {
|
||||
throw new BadRequestException('Timezone UUID is required');
|
||||
}
|
||||
|
||||
// Ensure the region exists
|
||||
const timezone = await this.timeZoneRepository.findOne({
|
||||
where: {
|
||||
uuid: updateTimezoneDataDto.timezoneUuid,
|
||||
},
|
||||
});
|
||||
if (!timezone) {
|
||||
throw new BadRequestException('Invalid timezone UUID');
|
||||
}
|
||||
|
||||
await this.userRepository.update(
|
||||
{ uuid: userUuid },
|
||||
{
|
||||
timezone: timezone,
|
||||
},
|
||||
);
|
||||
|
||||
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||
if (!updatedUser.timeZone) {
|
||||
throw new BadRequestException('Timezone update failed');
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: updatedUser.uuid,
|
||||
firstName: updatedUser.firstName,
|
||||
lastName: updatedUser.lastName,
|
||||
profilePicture: updatedUser.profilePicture,
|
||||
region: updatedUser.region,
|
||||
timeZoneUuid: updatedUser.timeZone,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new HttpException(
|
||||
err.message || 'User not found',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,11 +2,19 @@ import { Module } from '@nestjs/common';
|
||||
import { UserService } from './services/user.service';
|
||||
import { UserController } from './controllers/user.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService],
|
||||
providers: [
|
||||
UserService,
|
||||
UserRepository,
|
||||
RegionRepository,
|
||||
TimeZoneRepository,
|
||||
],
|
||||
exports: [UserService],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
Reference in New Issue
Block a user