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;
|
||||
roomNumber?: string | null;
|
||||
};
|
||||
salesforceHealthy?: boolean;
|
||||
}
|
||||
|
||||
// Salesforce Account interface based on the data model
|
||||
@ -81,7 +82,11 @@ export class UsersService {
|
||||
) {}
|
||||
|
||||
// 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 {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
@ -93,6 +98,7 @@ export class UsersService {
|
||||
emailVerified: user.emailVerified,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
salesforceHealthy,
|
||||
...extras,
|
||||
};
|
||||
}
|
||||
@ -194,7 +200,7 @@ export class UsersService {
|
||||
error: getErrorMessage(error),
|
||||
userId: validId,
|
||||
});
|
||||
return this.toEnhancedUser(user);
|
||||
return this.toEnhancedUser(user, {}, false);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to find user by ID", {
|
||||
@ -209,25 +215,29 @@ export class UsersService {
|
||||
if (!user) throw new Error("User not found");
|
||||
|
||||
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 {
|
||||
const account = (await this.salesforceService.getAccount(
|
||||
mapping.sfAccountId
|
||||
)) as SalesforceAccount | null;
|
||||
if (!account) return this.toEnhancedUser(user);
|
||||
if (!account) return this.toEnhancedUser(user, undefined, salesforceHealthy);
|
||||
|
||||
return this.toEnhancedUser(user, {
|
||||
company: account.Name?.trim() || user.company || undefined,
|
||||
email: user.email, // Keep original email for now
|
||||
phone: user.phone || undefined, // Keep original phone for now
|
||||
// Address temporarily disabled until field issues resolved
|
||||
});
|
||||
}, salesforceHealthy);
|
||||
} catch (error) {
|
||||
salesforceHealthy = false;
|
||||
this.logger.error("Failed to fetch Salesforce account data", {
|
||||
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");
|
||||
|
||||
try {
|
||||
const sobject = this.connection.sobject("Account") as unknown as {
|
||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
||||
};
|
||||
const sobject = this.connection.sobject("Account");
|
||||
|
||||
await sobject.update({
|
||||
await sobject.update?.({
|
||||
Id: accountId.trim(),
|
||||
WH_Account__c: whAccountValue.trim(),
|
||||
});
|
||||
@ -146,17 +144,13 @@ export class SalesforceAccountService {
|
||||
|
||||
if (existingAccount.totalSize > 0) {
|
||||
const accountId = existingAccount.records[0].Id;
|
||||
const sobject = this.connection.sobject("Account") as unknown as {
|
||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
||||
};
|
||||
await sobject.update({ Id: accountId, ...sfData });
|
||||
const sobject = this.connection.sobject("Account");
|
||||
await sobject.update?.({ Id: accountId, ...sfData });
|
||||
return { id: accountId, created: false };
|
||||
} else {
|
||||
const sobject = this.connection.sobject("Account") as unknown as {
|
||||
create: (data: Record<string, unknown>) => Promise<SalesforceCreateResult>;
|
||||
};
|
||||
const sobject = this.connection.sobject("Account");
|
||||
const result = await sobject.create(sfData);
|
||||
return { id: result.id, created: true };
|
||||
return { id: result.id || '', created: true };
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to upsert account", {
|
||||
@ -189,10 +183,8 @@ export class SalesforceAccountService {
|
||||
const validAccountId = this.validateId(accountId);
|
||||
|
||||
try {
|
||||
const sobject = this.connection.sobject("Account") as unknown as {
|
||||
update: (data: Record<string, unknown>) => Promise<unknown>;
|
||||
};
|
||||
await sobject.update({ Id: validAccountId, ...updates });
|
||||
const sobject = this.connection.sobject("Account");
|
||||
await sobject.update?.({ Id: validAccountId, ...updates });
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to update account", {
|
||||
error: getErrorMessage(error),
|
||||
|
||||
@ -9,7 +9,12 @@ import * as path from "path";
|
||||
|
||||
export interface SalesforceSObjectApi {
|
||||
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()
|
||||
@ -124,13 +129,101 @@ export class SalesforceConnection {
|
||||
}
|
||||
}
|
||||
|
||||
// Expose connection methods
|
||||
// Expose connection methods with automatic re-authentication
|
||||
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 {
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user