Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 225 additions & 20 deletions src/adapters/postgres/user-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ export class PostgresUserService implements IServicelocator {
if (username && userId === null) {
delete whereClause.userId;
whereClause.username = username;
}
}
const userDetails = await this.usersRepository.findOne({
where: whereClause,
select: [
Expand Down Expand Up @@ -910,9 +910,9 @@ export class PostgresUserService implements IServicelocator {
userDetails["tenantData"] = tenantData;

return userDetails;
}
}

async userTenantRoleData(userId: string) {
async userTenantRoleData(userId: string) {
const query = `
SELECT
DISTINCT ON (T."tenantId")
Expand All @@ -932,7 +932,7 @@ export class PostgresUserService implements IServicelocator {
WHERE
UTM."userId" = $1
ORDER BY
T."tenantId", UTM."Id";`;
T."tenantId", UTM."Id";`;

const result = await this.usersRepository.query(query, [userId]);
const combinedResult = [];
Expand Down Expand Up @@ -1932,7 +1932,7 @@ export class PostgresUserService implements IServicelocator {
}

async assignUserToTenantAndRoll(tenantsData, createdBy) {
try {
try {
const orgId = tenantsData?.tenantRoleMapping?.orgnizationId;
const userId = tenantsData?.userId;
const roleId = tenantsData?.tenantRoleMapping?.roleId;
Expand All @@ -1947,13 +1947,13 @@ export class PostgresUserService implements IServicelocator {
});
}

if (orgId) {
const data = await this.userOrgMappingRepository.save({
userId: userId,
orgId: orgId,
createdBy: createdBy,
updatedBy: createdBy,
});
if (orgId) {
const data = await this.userOrgMappingRepository.save({
userId: userId,
orgId: orgId,
createdBy: createdBy,
updatedBy: createdBy,
});
}

LoggerUtil.log(API_RESPONSES.USER_TENANT);
Expand Down Expand Up @@ -2422,9 +2422,10 @@ export class PostgresUserService implements IServicelocator {
async sendOtp(body: OtpSendDTO, response: Response) {
const apiId = APIID.SEND_OTP;
try {
const { mobile, reason, email, firstName, replacements, key } = body;
const { mobile, reason, email, firstName, replacements, key, whatsapp } =
body;
let notificationPayload, hash, expires, sentTo, otp;
if (mobile || email) {
if (mobile || email || whatsapp) {
otp = this.authUtils.generateOtp(this.otpDigits).toString();
}
if (mobile) {
Expand Down Expand Up @@ -2459,13 +2460,36 @@ export class PostgresUserService implements IServicelocator {
hash = result.hash;
expires = result.expires;
sentTo = email;
} else if (whatsapp) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apurvaubade we wont need this code here now. Please remove

// Send OTP on WhatsApp with SMS fallback
try {
const result = await this.sendOtpOnWhatsApp(whatsapp, otp, reason);
notificationPayload = result.notificationPayload;
hash = result.hash;
expires = result.expires;
sentTo = whatsapp;
} catch (error) {
// If WhatsApp fails, try SMS as fallback
LoggerUtil.error(
"WhatsApp OTP failed, falling back to SMS",
error.message,
apiId
);

// Use the same number for SMS
const result = await this.sendOTPOnMobile(whatsapp, otp, reason);
notificationPayload = result.notificationPayload;
hash = result.hash;
expires = result.expires;
sentTo = whatsapp; // Still show as sent to WhatsApp
}
} else {
// Neither mobile nor email provided
// Neither mobile nor email nor whatsapp provided
return APIResponse.error(
response,
apiId,
API_RESPONSES.BAD_REQUEST,
"Either mobile or email must be provided",
"Either mobile, email, or whatsapp must be provided",
HttpStatus.BAD_REQUEST
);
}
Expand All @@ -2476,7 +2500,9 @@ export class PostgresUserService implements IServicelocator {
message: `OTP sent to ${sentTo}`,
hash: `${hash}.${expires}`,
sendStatus:
notificationPayload?.result?.sms?.data?.[0] || notificationPayload,
notificationPayload?.result?.sms?.data?.[0] ||
notificationPayload?.result?.whatsapp?.data?.[0] ||
notificationPayload,
},
};

Expand Down Expand Up @@ -2574,7 +2600,7 @@ export class PostgresUserService implements IServicelocator {
async verifyOtp(body: OtpVerifyDTO, response: Response) {
const apiId = APIID.VERIFY_OTP;
try {
const { mobile, email, otp, hash, reason, username } = body;
const { mobile, email, whatsapp, otp, hash, reason, username } = body;

// Validate required fields for all requests
if (!otp || !hash || !reason) {
Expand Down Expand Up @@ -2614,8 +2640,8 @@ export class PostgresUserService implements IServicelocator {
let resetToken: string | null = null;

// Process based on reason
if (reason === "signup") {
if (!mobile && !email) {
if (reason === "signup" || reason === "login") {
if (!mobile && !email && !whatsapp) {
return APIResponse.error(
response,
apiId,
Expand All @@ -2628,6 +2654,8 @@ export class PostgresUserService implements IServicelocator {
identifier = this.formatMobileNumber(mobile);
} else if (email) {
identifier = email;
} else if (whatsapp) {
identifier = this.formatMobileNumber(whatsapp);
}
} else if (reason === "forgot") {
if (!username) {
Expand All @@ -2644,6 +2672,8 @@ export class PostgresUserService implements IServicelocator {
identifier = this.formatMobileNumber(mobile);
} else if (email) {
identifier = email;
} else if (whatsapp) {
identifier = this.formatMobileNumber(whatsapp);
}

// identifier = this.formatMobileNumber(mobile);
Expand Down Expand Up @@ -3160,4 +3190,179 @@ export class PostgresUserService implements IServicelocator {
// Don't throw the error to avoid affecting the main operation
}
}

//send WhatsApp Notification
async sendOtpOnWhatsApp(whatsapp: string, otp: string, reason: string) {
try {
// Step 1: Generate OTP hash with formatted number (consistent with verification)
const whatsappWithCode = this.formatMobileNumber(whatsapp);
const { hash, expires, expiresInMinutes } = this.generateOtpHash(
whatsappWithCode,
otp,
reason
);

// Step 2: Prepare WhatsApp notification payload
const notificationPayload = await this.whatsappNotification(
whatsapp,
otp,
reason
);

return { notificationPayload, hash, expires, expiresInMinutes };
} catch (error) {
throw new Error(`Failed to send OTP via WhatsApp: ${error.message}`);
}
}

//send WhatsApp Notification
async whatsappNotification(whatsapp: string, otp: string, reason: string) {
try {
// Get environment variables
const templateId = this.configService.get<string>("WHATSAPP_TEMPLATE_ID");
const apiKey = this.configService.get<string>("WHATSAPP_GUPSHUP_API_KEY");
const gupshupSource = this.configService.get<string>(
"WHATSAPP_GUPSHUP_SOURCE"
);

// Check if environment variables are set
if (!templateId || !apiKey || !gupshupSource) {
// Log warning instead of throwing error
LoggerUtil.error(
"WhatsApp environment variables not configured",
"WhatsApp OTP sending is disabled. Please configure WHATSAPP_TEMPLATE_ID, WHATSAPP_GUPSHUP_API_KEY, and WHATSAPP_GUPSHUP_SOURCE",
"WHATSAPP_CONFIG"
);

// Return a mock response for testing
return {
result: {
whatsapp: {
data: [{ status: "skipped", message: "WhatsApp not configured" }],
},
},
};
}

// WhatsApp notification Body
const notificationPayload = {
whatsapp: {
to: [whatsapp],
templateId: templateId,
templateParams: [otp],
gupshupSource: gupshupSource, // Use environment variable for sender number
gupshupApiKey: apiKey,
},
};

// Send Axios request to raw notification endpoint
const mailSend = await this.notificationRequest.sendRawNotification(
notificationPayload
);

// Check for errors in the response
if (
mailSend?.result?.whatsapp?.errors &&
mailSend.result.whatsapp.errors.length > 0
) {
const errorMessages = mailSend.result.whatsapp.errors.map(
(error: { error: string }) => error.error
);
const combinedErrorMessage = errorMessages.join(", ");
throw new Error(
`${API_RESPONSES.WHATSAPP_ERROR} :${combinedErrorMessage}`
);
}

// Check if the response indicates success
if (!mailSend?.result?.whatsapp) {
throw new Error("Invalid response from notification service");
}

return mailSend;
} catch (error) {
LoggerUtil.error(API_RESPONSES.WHATSAPP_ERROR, error.message);
throw new Error(
`${API_RESPONSES.WHATSAPP_NOTIFICATION_ERROR}: ${error.message}`
);
}
}

async findUserByIdentifier(identifier: string): Promise<any> {
try {
console.log(`[DEBUG] Searching for user with identifier: ${identifier}`);

// Try to find user by email
let user = await this.usersRepository.findOne({
where: { email: identifier }
});
console.log(`[DEBUG] Email search result:`, user ? 'User found' : 'No user found');

if (!user) {
// Try to find user by username
user = await this.usersRepository.findOne({
where: { username: identifier }
});
console.log(`[DEBUG] Username search result:`, user ? 'User found' : 'No user found');
}

if (!user) {
// Try to find user by mobile (if identifier is numeric)
if (/^\d+$/.test(identifier)) {
user = await this.usersRepository.findOne({
where: { mobile: parseInt(identifier) }
});
console.log(`[DEBUG] Mobile search result:`, user ? 'User found' : 'No user found');
}
}

// If still no user found, try case-insensitive search
if (!user) {
console.log(`[DEBUG] Trying case-insensitive search...`);
const caseInsensitiveUser = await this.usersRepository
.createQueryBuilder('user')
.where('LOWER(user.email) = LOWER(:email)', { email: identifier })
.getOne();

if (caseInsensitiveUser) {
console.log(`[DEBUG] Found user with case-insensitive search:`, caseInsensitiveUser);
user = caseInsensitiveUser;
}
}

// Final fallback: direct database query
if (!user) {
console.log(`[DEBUG] Trying direct database query...`);
try {
const rawUser = await this.dataSource.query(
'SELECT * FROM public."Users" WHERE "email" = $1 OR "username" = $1',
[identifier]
);
console.log(`[DEBUG] Raw query result:`, rawUser);

if (rawUser && rawUser.length > 0) {
const rawUserData = rawUser[0];
user = {
userId: rawUserData.userId || rawUserData.id,
email: rawUserData.email,
username: rawUserData.username,
mobile: rawUserData.mobile,
firstName: rawUserData.firstName,
lastName: rawUserData.lastName
} as any;
console.log(`[DEBUG] Converted raw user:`, user);
}
} catch (rawError) {
console.error(`[DEBUG] Raw query error:`, rawError);
}
}

console.log(`[DEBUG] Final result:`, user ? `User found: ${user.userId}` : 'No user found');
return user;
} catch (error) {
console.error('[DEBUG] Error in findUserByIdentifier:', error);
LoggerUtil.error('Error finding user by identifier', error.message);
return null;
}
}
}
1 change: 1 addition & 0 deletions src/adapters/userservicelocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ export interface IServicelocator {
body: SendPasswordResetOTPDto,
response: Response
): Promise<any>;
findUserByIdentifier(identifier: string): Promise<any>;
}
Loading