diff --git a/src/adapters/postgres/user-adapter.ts b/src/adapters/postgres/user-adapter.ts index e3b2531..a51d57c 100644 --- a/src/adapters/postgres/user-adapter.ts +++ b/src/adapters/postgres/user-adapter.ts @@ -69,8 +69,6 @@ export class PostgresUserService implements IServicelocator { private tenantsRepository: Repository, @InjectRepository(UserRoleMapping) private userRoleMappingRepository: Repository, - @InjectRepository(Cohort) - private cohortRepository: Repository, @InjectRepository(Role) private roleRepository: Repository, private fieldsService: PostgresFieldsService, @@ -404,7 +402,7 @@ export class PostgresUserService implements IServicelocator { whereCondition += ` AND `; } if (userKeys.includes(key)) { - if (key === "name") { + if (key === "firstName") { whereCondition += ` U."${key}" ILIKE '%${value}%'`; } else { if (key === "status" || key === "email") { @@ -498,7 +496,7 @@ export class PostgresUserService implements IServicelocator { } //Get user core fields data - const query = `SELECT U."userId", U."username",U."email", U."name", R."name" AS role, U."mobile", U."createdBy",U."updatedBy", U."createdAt", U."updatedAt", U.status, COUNT(*) OVER() AS total_count + const query = `SELECT U."userId", U."username",U."email", U."firstName", U."middleName", U."lastName", U."gender", U."dob", R."name" AS role, U."mobile", U."createdBy",U."updatedBy", U."createdAt", U."updatedAt", U.status, COUNT(*) OVER() AS total_count FROM public."Users" U LEFT JOIN public."CohortMembers" CM ON CM."userId" = U."userId" @@ -688,7 +686,9 @@ export class PostgresUserService implements IServicelocator { select: [ "userId", "username", - "name", + "firstName", + "middleName", + "lastName", "mobile", "email", "temporaryPassword", @@ -900,17 +900,31 @@ export class PostgresUserService implements IServicelocator { return deviceIds; } - async updateBasicUserDetails(userId, userData: Partial): Promise { - const user = await this.usersRepository.findOne({ - where: { userId: userId }, - }); - if (!user) { - return null; + async updateBasicUserDetails(userId: string, userData: Partial): Promise { + try { + // Fetch the user by ID + const user = await this.usersRepository.findOne({ where: { userId } }); + + if (!user) { + // If the user is not found, return null + return null; + } + + // Update the user's details + await this.usersRepository.update(userId, userData); + + // Fetch and return the updated user + const updatedUser = await this.usersRepository.findOne({ where: { userId } }); + + return updatedUser; + } catch (error) { + // Re-throw or handle the error as needed + throw new Error('An error occurred while updating user details'); } - Object.assign(user, userData); - return this.usersRepository.save(user); } + + async createUser( request: any, userCreateDto: UserCreateDto, @@ -970,7 +984,7 @@ export class PostgresUserService implements IServicelocator { const userSchema = new UserCreateDto(userCreateDto); let errKeycloak = ""; - let resKeycloak = ""; + let resKeycloak; const keycloakResponse = await getKeycloakAdminToken(); const token = keycloakResponse.data.access_token; @@ -988,28 +1002,36 @@ export class PostgresUserService implements IServicelocator { ); } - resKeycloak = await createUserInKeyCloak(userSchema, token).catch( - (error) => { - LoggerUtil.error( - `${API_RESPONSES.SERVER_ERROR}: ${request.url}`, - `KeyCloak Error: ${error.message}`, - apiId - ); + resKeycloak = await createUserInKeyCloak(userSchema, token) - errKeycloak = error.response?.data.errorMessage; + + if(resKeycloak.statusCode !== 201 ){ + if (resKeycloak.statusCode === 409) { + LoggerUtil.log(API_RESPONSES.EMAIL_EXIST, apiId); + + return APIResponse.error( + response, + apiId, + API_RESPONSES.EMAIL_EXIST, + `${resKeycloak.message} ${resKeycloak.email}`, + HttpStatus.CONFLICT + ); + }else{ + LoggerUtil.log(API_RESPONSES.SERVER_ERROR, apiId); return APIResponse.error( response, apiId, API_RESPONSES.SERVER_ERROR, - `${errKeycloak}`, + `${resKeycloak.message}`, HttpStatus.INTERNAL_SERVER_ERROR ); } - ); + } LoggerUtil.log(API_RESPONSES.USER_CREATE_KEYCLOAK, apiId); - userCreateDto.userId = resKeycloak; + + userCreateDto.userId = resKeycloak.userId; // if cohort given then check for academic year @@ -1294,17 +1316,15 @@ export class PostgresUserService implements IServicelocator { response: Response ) { const user = new User(); - (user.username = userCreateDto?.username), - (user.name = userCreateDto?.name), - (user.email = userCreateDto?.email), - (user.mobile = Number(userCreateDto?.mobile) || null), - (user.createdBy = userCreateDto?.createdBy || userCreateDto?.userId), - (user.updatedBy = userCreateDto?.updatedBy || userCreateDto?.userId), - (user.userId = userCreateDto?.userId), - (user.state = userCreateDto?.state), - (user.district = userCreateDto?.district), - (user.address = userCreateDto?.address), - (user.pincode = userCreateDto?.pincode); + user.userId = userCreateDto?.userId, + user.username = userCreateDto?.username, + user.firstName = userCreateDto?.firstName, + user.middleName = userCreateDto?.middleName, + user.lastName = userCreateDto?.lastName, + user.gender = userCreateDto?.gender, + user.email = userCreateDto?.email, + user.mobile = Number(userCreateDto?.mobile) || null, + user.createdBy = userCreateDto?.createdBy || userCreateDto?.createdBy; if (userCreateDto?.dob) { user.dob = new Date(userCreateDto.dob); diff --git a/src/common/utils/keycloak.adapter.util.ts b/src/common/utils/keycloak.adapter.util.ts index a86547c..848cb0a 100644 --- a/src/common/utils/keycloak.adapter.util.ts +++ b/src/common/utils/keycloak.adapter.util.ts @@ -1,5 +1,7 @@ import { API_RESPONSES } from "./response.messages"; import { LoggerUtil } from "src/common/logger/LoggerUtil"; +const axios = require("axios"); + function getUserRole(userRoles: string[]) { if (userRoles.includes("systemAdmin")) { return "systemAdmin"; @@ -53,65 +55,79 @@ async function getKeycloakAdminToken() { return res; } + async function createUserInKeyCloak(query, token) { - const axios = require("axios"); - const name = query.name; - const nameParts = name.split(" "); - let lname = ""; - - if (nameParts[2]) { - lname = nameParts[2]; - } else if (nameParts[1]) { - lname = nameParts[1]; - } if (!query.password) { return "User cannot be created, Password missing"; } const data = JSON.stringify({ - firstName: nameParts[0], - lastName: lname, - enabled: "true", + firstName: query.firstName, + lastName: query.lastName, + email: query.email || null, // Use `||` for simpler null/undefined handling username: query.username, - // groups: [getUserGroup(query.role)], + enabled: true, // Changed "true" (string) to true (boolean) credentials: [ { - temporary: "false", + temporary: false, // Changed "false" (string) to false (boolean) type: "password", value: query.password, }, ], }); + console.log("Payload for Keycloak:", data); + const config = { method: "post", - url: process.env.KEYCLOAK + process.env.KEYCLOAK_ADMIN, + url: `${process.env.KEYCLOAK}${process.env.KEYCLOAK_ADMIN}`, headers: { "Content-Type": "application/json", - Authorization: "Bearer " + token, + Authorization: `Bearer ${token}`, }, - data: data, + data, }; - let userResponse; - // try { - // userResponse = await axios(config); - // } catch (e) { - // return e; - // } - // const userString = userResponse.headers.location; - // const index = userString.lastIndexOf("/"); - // const userId = userString.substring(index + 1); - - // return userId; try { - const userResponse = await axios(config); - return userResponse.headers.location.split("/").pop(); + // Make the request and wait for the response + const response = await axios(config); + + // Log and return the created user's ID + console.log("User created successfully:", response.data); + const userId = response.headers.location.split("/").pop(); // Extract user ID from the location header + console.log("Created User ID:", userId); + return { statusCode: response.status, message: "User created successfully", userId : userId }; } catch (error) { - return "Error creating user: " + error.response.data.error; + // Handle errors and log relevant details + if (error.response) { + console.error("Error Response Status:", error.response.status); + console.error("Error Response Data:", error.response.data); + console.error("Error Response Headers:", error.response.headers); + + return { + statusCode: error.response.status, + message: error.response.data.errorMessage || "Error occurred during user creation", + email: query.email || "No email provided", + }; + } else if (error.request) { + console.error("No response received:", error.request); + return { + statusCode: 500, + message: "No response received from Keycloak", + email: query.email || "No email provided", + }; + } else { + console.error("Error setting up request:", error.message); + return { + statusCode: 500, + message: `Error setting up request: ${error.message}`, + email: query.email || "No email provided", + }; + } } } + async function checkIfEmailExistsInKeycloak(email, token) { const axios = require("axios"); const config = { diff --git a/src/common/utils/response.messages.ts b/src/common/utils/response.messages.ts index 1e1e864..992af67 100644 --- a/src/common/utils/response.messages.ts +++ b/src/common/utils/response.messages.ts @@ -1,5 +1,6 @@ export const API_RESPONSES = { USERNAME_NOT_FOUND: "Username does not exist", + EMAIL_EXIST: "Email already exists", USER_NOT_FOUND: "User does not exist", FORGOT_PASSWORD_SUCCESS: "Forgot password Reset successfully", EMAIL_NOT_FOUND_FOR_RESET: diff --git a/src/user/dto/user-create.dto.ts b/src/user/dto/user-create.dto.ts index 3a077c0..4e6c1dd 100644 --- a/src/user/dto/user-create.dto.ts +++ b/src/user/dto/user-create.dto.ts @@ -9,6 +9,8 @@ import { IsUUID, ValidateNested, IsOptional, + Length, + IsEnum, } from "class-validator"; import { User } from "../entities/user-entity"; import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; @@ -70,9 +72,34 @@ export class UserCreateDto { @IsNotEmpty() username: string; - @ApiProperty({ type: () => String }) + @ApiProperty({ type: String, description: 'First name of the user', maxLength: 50 }) + @Expose() + @IsString() + @Length(1, 50) + firstName: string; + + @ApiPropertyOptional({ type: String, description: 'Middle name of the user (optional)', maxLength: 50, required: false }) + @Expose() + @IsOptional() + @IsString() + @Length(0, 50) + middleName?: string; + + @ApiProperty({ type: String, description: 'Last name of the user', maxLength: 50 }) @Expose() - name: string; + @IsString() + @Length(1, 50) + lastName: string; + + @ApiProperty({ + type: String, + description: 'Gender of the user', + enum: ['male', 'female', 'transgender'] + }) + @Expose() + @IsEnum(['male', 'female', 'transgender']) + gender: string; + @ApiPropertyOptional({ type: String, diff --git a/src/user/dto/user-update.dto.ts b/src/user/dto/user-update.dto.ts index 4ec0b24..3bbe37d 100644 --- a/src/user/dto/user-update.dto.ts +++ b/src/user/dto/user-update.dto.ts @@ -6,6 +6,7 @@ import { IsNotEmpty, IsEnum, ValidateIf, + Length, } from "class-validator"; import { Expose, Type } from "class-transformer"; import { UserStatus } from "../entities/user-entity"; @@ -22,10 +23,36 @@ class UserDataDTO { @IsOptional() username: string; - @ApiProperty({ type: () => String }) + @ApiProperty({ type: String, description: 'First name of the user', maxLength: 50 }) + @Expose() + @IsOptional() + @IsString() + @Length(1, 50) + firstName?: string; + + @ApiProperty({ type: String, description: 'Middle name of the user (optional)', maxLength: 50, required: false }) + @Expose() + @IsOptional() @IsString() + @Length(0, 50) + middleName?: string; + + @ApiProperty({ type: String, description: 'Last name of the user', maxLength: 50 }) + @Expose() + @IsOptional() + @IsString() + @Length(1, 50) + lastName?: string; + + @ApiProperty({ + type: String, + description: 'Gender of the user', + enum: ['male', 'female', 'transgender'] + }) + @Expose() + @IsEnum(['male', 'female', 'transgender']) @IsOptional() - name: string; + gender?: string; @ApiProperty({ type: () => String }) @IsString() diff --git a/src/user/entities/user-entity.ts b/src/user/entities/user-entity.ts index c0233bd..bbca3e2 100644 --- a/src/user/entities/user-entity.ts +++ b/src/user/entities/user-entity.ts @@ -22,8 +22,17 @@ export class User { @Column({ unique: true }) username: string; - @Column() - name: string; + @Column({ type: 'varchar', length: 50, nullable: false }) + firstName: string; + + @Column({ type: 'varchar', length: 50, nullable: true }) + middleName: string; + + @Column({ type: 'varchar', length: 50, nullable: false }) + lastName: string; + + @Column({ type: 'enum', enum: ['male', 'female', 'transgender'], nullable: false }) + gender: string; @Column({ type: "date", nullable: true }) dob: Date; diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 1532d23..65f49df 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -155,7 +155,6 @@ export class UserController { public async updateUser( @Headers() headers, @Param("userid") userId: string, - @Req() request: Request, @Body() userUpdateDto: UserUpdateDTO, @Res() response: Response ) { @@ -163,7 +162,7 @@ export class UserController { userUpdateDto.userId = userId; return await this.userAdapter .buildUserAdapter() - .updateUser(userUpdateDto, response); + .updateUser( userUpdateDto, response); } @UseFilters(new AllExceptionsFilter(APIID.USER_LIST))