Enhance user service and Salesforce connection handling
- Added 'salesforceHealthy' property to EnhancedUser interface for tracking Salesforce account health. - Updated toEnhancedUser method to accept 'salesforceHealthy' parameter for improved user data handling. - Modified user retrieval logic to correctly reflect Salesforce account status. - Refactored Salesforce connection service to handle session expiration with automatic re-authentication for SObject operations.
This commit is contained in:
parent
cc2a6a3046
commit
26eb8a7341
@ -26,6 +26,7 @@ export interface EnhancedUser extends Omit<User, "createdAt" | "updatedAt"> {
|
|||||||
buildingName?: string | null;
|
buildingName?: string | null;
|
||||||
roomNumber?: string | null;
|
roomNumber?: string | null;
|
||||||
};
|
};
|
||||||
|
salesforceHealthy?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Salesforce Account interface based on the data model
|
// Salesforce Account interface based on the data model
|
||||||
@ -81,7 +82,11 @@ export class UsersService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
// Helper function to convert Prisma user to EnhancedUser type
|
// Helper function to convert Prisma user to EnhancedUser type
|
||||||
private toEnhancedUser(user: PrismaUser, extras: Partial<EnhancedUser> = {}): EnhancedUser {
|
private toEnhancedUser(
|
||||||
|
user: PrismaUser,
|
||||||
|
extras: Partial<EnhancedUser> = {},
|
||||||
|
salesforceHealthy: boolean = true
|
||||||
|
): EnhancedUser {
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
@ -93,6 +98,7 @@ export class UsersService {
|
|||||||
emailVerified: user.emailVerified,
|
emailVerified: user.emailVerified,
|
||||||
createdAt: user.createdAt,
|
createdAt: user.createdAt,
|
||||||
updatedAt: user.updatedAt,
|
updatedAt: user.updatedAt,
|
||||||
|
salesforceHealthy,
|
||||||
...extras,
|
...extras,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -194,7 +200,7 @@ export class UsersService {
|
|||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
userId: validId,
|
userId: validId,
|
||||||
});
|
});
|
||||||
return this.toEnhancedUser(user);
|
return this.toEnhancedUser(user, {}, false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to find user by ID", {
|
this.logger.error("Failed to find user by ID", {
|
||||||
@ -209,25 +215,29 @@ export class UsersService {
|
|||||||
if (!user) throw new Error("User not found");
|
if (!user) throw new Error("User not found");
|
||||||
|
|
||||||
const mapping = await this.mappingsService.findByUserId(userId);
|
const mapping = await this.mappingsService.findByUserId(userId);
|
||||||
if (!mapping?.sfAccountId) return this.toEnhancedUser(user);
|
if (!mapping?.sfAccountId) return this.toEnhancedUser(user, {}, true);
|
||||||
|
|
||||||
|
let salesforceHealthy = true;
|
||||||
try {
|
try {
|
||||||
const account = (await this.salesforceService.getAccount(
|
const account = (await this.salesforceService.getAccount(
|
||||||
mapping.sfAccountId
|
mapping.sfAccountId
|
||||||
)) as SalesforceAccount | null;
|
)) as SalesforceAccount | null;
|
||||||
if (!account) return this.toEnhancedUser(user);
|
if (!account) return this.toEnhancedUser(user, undefined, salesforceHealthy);
|
||||||
|
|
||||||
return this.toEnhancedUser(user, {
|
return this.toEnhancedUser(user, {
|
||||||
company: account.Name?.trim() || user.company || undefined,
|
company: account.Name?.trim() || user.company || undefined,
|
||||||
email: user.email, // Keep original email for now
|
email: user.email, // Keep original email for now
|
||||||
phone: user.phone || undefined, // Keep original phone for now
|
phone: user.phone || undefined, // Keep original phone for now
|
||||||
// Address temporarily disabled until field issues resolved
|
// Address temporarily disabled until field issues resolved
|
||||||
});
|
}, salesforceHealthy);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
salesforceHealthy = false;
|
||||||
this.logger.error("Failed to fetch Salesforce account data", {
|
this.logger.error("Failed to fetch Salesforce account data", {
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
|
userId,
|
||||||
|
sfAccountId: mapping.sfAccountId,
|
||||||
});
|
});
|
||||||
return this.toEnhancedUser(user);
|
return this.toEnhancedUser(user, undefined, salesforceHealthy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,11 +93,9 @@ export class SalesforceAccountService {
|
|||||||
if (!whAccountValue?.trim()) throw new Error("WH Account value is required");
|
if (!whAccountValue?.trim()) throw new Error("WH Account value is required");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sobject = this.connection.sobject("Account") as unknown as {
|
const sobject = this.connection.sobject("Account");
|
||||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
|
||||||
};
|
|
||||||
|
|
||||||
await sobject.update({
|
await sobject.update?.({
|
||||||
Id: accountId.trim(),
|
Id: accountId.trim(),
|
||||||
WH_Account__c: whAccountValue.trim(),
|
WH_Account__c: whAccountValue.trim(),
|
||||||
});
|
});
|
||||||
@ -146,17 +144,13 @@ export class SalesforceAccountService {
|
|||||||
|
|
||||||
if (existingAccount.totalSize > 0) {
|
if (existingAccount.totalSize > 0) {
|
||||||
const accountId = existingAccount.records[0].Id;
|
const accountId = existingAccount.records[0].Id;
|
||||||
const sobject = this.connection.sobject("Account") as unknown as {
|
const sobject = this.connection.sobject("Account");
|
||||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
await sobject.update?.({ Id: accountId, ...sfData });
|
||||||
};
|
|
||||||
await sobject.update({ Id: accountId, ...sfData });
|
|
||||||
return { id: accountId, created: false };
|
return { id: accountId, created: false };
|
||||||
} else {
|
} else {
|
||||||
const sobject = this.connection.sobject("Account") as unknown as {
|
const sobject = this.connection.sobject("Account");
|
||||||
create: (data: Record<string, unknown>) => Promise<SalesforceCreateResult>;
|
|
||||||
};
|
|
||||||
const result = await sobject.create(sfData);
|
const result = await sobject.create(sfData);
|
||||||
return { id: result.id, created: true };
|
return { id: result.id || '', created: true };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to upsert account", {
|
this.logger.error("Failed to upsert account", {
|
||||||
@ -189,10 +183,8 @@ export class SalesforceAccountService {
|
|||||||
const validAccountId = this.validateId(accountId);
|
const validAccountId = this.validateId(accountId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sobject = this.connection.sobject("Account") as unknown as {
|
const sobject = this.connection.sobject("Account");
|
||||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
await sobject.update?.({ Id: validAccountId, ...updates });
|
||||||
};
|
|
||||||
await sobject.update({ Id: validAccountId, ...updates });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to update account", {
|
this.logger.error("Failed to update account", {
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
|
|||||||
@ -9,7 +9,12 @@ import * as path from "path";
|
|||||||
|
|
||||||
export interface SalesforceSObjectApi {
|
export interface SalesforceSObjectApi {
|
||||||
create: (data: Record<string, unknown>) => Promise<{ id?: string }>;
|
create: (data: Record<string, unknown>) => Promise<{ id?: string }>;
|
||||||
update?: (data: Record<string, unknown>) => Promise<unknown>;
|
update?: (data: Record<string, unknown> & { Id: string }) => Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SalesforceRetryableSObjectApi extends SalesforceSObjectApi {
|
||||||
|
create: (data: Record<string, unknown>) => Promise<{ id?: string }>;
|
||||||
|
update?: (data: Record<string, unknown> & { Id: string }) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -124,13 +129,101 @@ export class SalesforceConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose connection methods
|
// Expose connection methods with automatic re-authentication
|
||||||
async query(soql: string): Promise<unknown> {
|
async query(soql: string): Promise<unknown> {
|
||||||
return await this.connection.query(soql);
|
try {
|
||||||
|
return await this.connection.query(soql);
|
||||||
|
} catch (error: any) {
|
||||||
|
// Check if this is a session expiration error
|
||||||
|
if (this.isSessionExpiredError(error)) {
|
||||||
|
this.logger.warn("Salesforce session expired, attempting to re-authenticate");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Re-authenticate
|
||||||
|
await this.connect();
|
||||||
|
|
||||||
|
// Retry the query once
|
||||||
|
this.logger.debug("Retrying query after re-authentication");
|
||||||
|
return await this.connection.query(soql);
|
||||||
|
} catch (retryError) {
|
||||||
|
this.logger.error("Failed to re-authenticate or retry query", {
|
||||||
|
originalError: getErrorMessage(error),
|
||||||
|
retryError: getErrorMessage(retryError),
|
||||||
|
});
|
||||||
|
throw retryError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-throw other errors as-is
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSessionExpiredError(error: any): boolean {
|
||||||
|
// Check for various session expiration indicators
|
||||||
|
const errorMessage = getErrorMessage(error).toLowerCase();
|
||||||
|
const errorCode = error?.errorCode || error?.name || '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
errorCode === 'INVALID_SESSION_ID' ||
|
||||||
|
errorMessage.includes('session expired') ||
|
||||||
|
errorMessage.includes('invalid session') ||
|
||||||
|
errorMessage.includes('invalid_session_id') ||
|
||||||
|
(error?.status === 401 && errorMessage.includes('unauthorized'))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sobject(type: string): SalesforceSObjectApi {
|
sobject(type: string): SalesforceSObjectApi {
|
||||||
return this.connection.sobject(type) as unknown as SalesforceSObjectApi;
|
const originalSObject = this.connection.sobject(type);
|
||||||
|
|
||||||
|
// Return a wrapper that handles session expiration for SObject operations
|
||||||
|
return {
|
||||||
|
create: async (data: Record<string, unknown>) => {
|
||||||
|
try {
|
||||||
|
return await originalSObject.create(data);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (this.isSessionExpiredError(error)) {
|
||||||
|
this.logger.warn("Salesforce session expired during SObject create, attempting to re-authenticate");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.connect();
|
||||||
|
const newSObject = this.connection.sobject(type);
|
||||||
|
return await newSObject.create(data);
|
||||||
|
} catch (retryError) {
|
||||||
|
this.logger.error("Failed to re-authenticate or retry SObject create", {
|
||||||
|
originalError: getErrorMessage(error),
|
||||||
|
retryError: getErrorMessage(retryError),
|
||||||
|
});
|
||||||
|
throw retryError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async (data: Record<string, unknown> & { Id: string }) => {
|
||||||
|
try {
|
||||||
|
return await originalSObject.update(data as any);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (this.isSessionExpiredError(error)) {
|
||||||
|
this.logger.warn("Salesforce session expired during SObject update, attempting to re-authenticate");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.connect();
|
||||||
|
const newSObject = this.connection.sobject(type);
|
||||||
|
return await newSObject.update(data as any);
|
||||||
|
} catch (retryError) {
|
||||||
|
this.logger.error("Failed to re-authenticate or retry SObject update", {
|
||||||
|
originalError: getErrorMessage(error),
|
||||||
|
retryError: getErrorMessage(retryError),
|
||||||
|
});
|
||||||
|
throw retryError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user