-
Notifications
You must be signed in to change notification settings - Fork 202
fix: #448 #481
base: main
Are you sure you want to change the base?
fix: #448 #481
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import { Module } from '@nestjs/common'; | ||
| import { HttpModule } from '@nestjs/axios'; | ||
| import { AffinityConnectionService } from './services/affinity/affinity.service'; | ||
| import { CrmConnectionsService } from './services/crm.connection.service'; | ||
| import { PrismaService } from '@@core/prisma/prisma.service'; | ||
| import { LoggerService } from '@@core/logger/logger.service'; | ||
|
|
@@ -15,7 +17,7 @@ import { AttioConnectionService } from './services/attio/attio.service'; | |
| import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider removing the trailing comma after HttpModule for consistency. |
||
| @Module({ | ||
| imports: [WebhookModule], | ||
| imports: [WebhookModule, HttpModule,], | ||
| providers: [ | ||
| CrmConnectionsService, | ||
| PrismaService, | ||
|
|
@@ -31,6 +33,7 @@ import { ConnectionsStrategiesService } from '@@core/connections-strategies/conn | |
| ZohoConnectionService, | ||
| ZendeskConnectionService, | ||
| PipedriveConnectionService, | ||
| AffinityConnectionService, | ||
| ], | ||
| exports: [CrmConnectionsService], | ||
| }) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,105 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { HttpService } from '@nestjs/axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { lastValueFrom } from 'rxjs'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Injectable } from '@nestjs/common'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CallbackParams, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ICrmConnectionService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RefreshParams, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '../../types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PrismaService } from '@@core/prisma/prisma.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import axios from 'axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { v4 as uuidv4 } from 'uuid'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Action, handleServiceError } from '@@core/utils/errors'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { EnvironmentService } from '@@core/environment/environment.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { EncryptionService } from '@@core/encryption/encryption.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ServiceRegistry } from '../registry.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { LoggerService } from '@@core/logger/logger.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OAuth2AuthData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONNECTORS_METADATA, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| providerToType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@panora/shared'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AuthStrategy } from '@panora/shared'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConnectionUtils } from '@@core/connections/@utils'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Injectable() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class AffinityConnectionService implements ICrmConnectionService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private prisma: PrismaService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private logger: LoggerService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private env: EnvironmentService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private cryptoService: EncryptionService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private registry: ServiceRegistry, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private httpService: HttpService, // Add HttpService for making HTTP requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.setContext(AffinityConnectionService.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.registry.registerService('affinity', this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async handleCallback(opts: CallbackParams): Promise<Connection> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { linkedUserId, projectId, code } = opts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clientId = this.env.get('AFFINITY_CLIENT_ID'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clientSecret = this.env.get('AFFINITY_CLIENT_SECRET'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const redirectUri = this.env.get('AFFINITY_REDIRECT_URI'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tokenResponse = await lastValueFrom(this.httpService.post('https://api.affinity.co/oauth/token', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| grant_type: 'authorization_code', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: code, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirect_uri: redirectUri, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_id: clientId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_secret: clientSecret, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { access_token, refresh_token, expires_in } = tokenResponse.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const connection = await this.prisma.connection.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linkedUserId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accessToken: access_token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refreshToken: refresh_token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expiresAt: new Date(Date.now() + expires_in * 1000), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider: 'affinity', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return connection; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.error('Error handling OAuth callback for Affinity', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Failed to handle OAuth callback for Affinity'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async handleTokenRefresh(opts: RefreshParams): Promise<any> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { connectionId, refreshToken } = opts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clientId = this.env.get('AFFINITY_CLIENT_ID'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clientSecret = this.env.get('AFFINITY_CLIENT_SECRET'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tokenResponse = await lastValueFrom(this.httpService.post('https://api.affinity.co/oauth/token', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| grant_type: 'refresh_token', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refresh_token: refreshToken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_id: clientId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_secret: clientSecret, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { access_token, refresh_token, expires_in } = tokenResponse.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.prisma.connection.update({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| where: { id: connectionId }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accessToken: access_token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refreshToken: refresh_token, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expiresAt: new Date(Date.now() + expires_in * 1000), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { access_token, refresh_token, expires_in }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.error('Error refreshing token for Affinity', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Failed to refresh token for Affinity'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+75
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - throw new Error('Failed to refresh token for Affinity');
+ throw new Error(`Failed to refresh token for Affinity: ${error.message}`);Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import { Injectable } from '@nestjs/common'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a newline at the end of the file for POSIX compliance. |
||
| import { IContactService } from '@crm/contact/types'; | ||
| import { CrmObject } from '@crm/@lib/@types'; | ||
| import axios from 'axios'; | ||
| import { PrismaService } from '@@core/prisma/prisma.service'; | ||
| import { LoggerService } from '@@core/logger/logger.service'; | ||
| import { ActionType, handleServiceError } from '@@core/utils/errors'; | ||
| import { EncryptionService } from '@@core/encryption/encryption.service'; | ||
| import { ApiResponse } from '@@core/utils/types'; | ||
| import { ServiceRegistry } from '../registry.service'; | ||
| import { AffinityContactInput, AffinityContactOutput } from './types'; | ||
|
|
||
| @Injectable() | ||
| export class AffinityService implements IContactService { | ||
| constructor( | ||
| private prisma: PrismaService, | ||
| private logger: LoggerService, | ||
| private cryptoService: EncryptionService, | ||
| private registry: ServiceRegistry, | ||
| private httpService: HttpService, | ||
| ) { | ||
| this.logger.setContext( | ||
| 'CRM:Contact:' + AffinityService.name, | ||
| ); | ||
| this.registry.registerService('affinity', this); | ||
| } | ||
|
|
||
| async addContact( | ||
| contactData: AffinityContactInput, | ||
| linkedUserId: string, | ||
| ): Promise<ApiResponse<AffinityContactOutput>> { | ||
| try { | ||
| const accessToken = await this.getAccessToken(linkedUserId); | ||
| const response = await lastValueFrom( | ||
| this.httpService.post('https://api.affinity.co/contacts', contactData, { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| }) | ||
| ); | ||
|
|
||
| return { | ||
| success: true, | ||
| data: response.data, | ||
| }; | ||
| } catch (error) { | ||
| this.logger.error('Error adding contact to Affinity', error); | ||
| return { | ||
| success: false, | ||
| message: 'Failed to add contact to Affinity', | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| async syncContacts( | ||
| linkedUserId: string, | ||
| ): Promise<ApiResponse<AffinityContactOutput[]>> { | ||
| try { | ||
| const accessToken = await this.getAccessToken(linkedUserId); | ||
| const response = await lastValueFrom( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| this.httpService.get('https://api.affinity.co/contacts', { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| }) | ||
| ); | ||
|
|
||
| return { | ||
| success: true, | ||
| data: response.data, | ||
| }; | ||
| } catch (error) { | ||
| this.logger.error('Error syncing contacts from Affinity', error); | ||
| return { | ||
| success: false, | ||
| message: 'Failed to sync contacts from Affinity', | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| private async getAccessToken(linkedUserId: string): Promise<string> { | ||
| const connection = await this.prisma.connection.findUnique({ | ||
| where: { | ||
| linkedUserId, | ||
| provider: 'affinity', | ||
| }, | ||
| }); | ||
|
|
||
| if (!connection) { | ||
| throw new Error('No Affinity connection found for user'); | ||
| } | ||
|
|
||
| if (new Date(connection.expiresAt) < new Date()) { | ||
| const refreshResponse = await lastValueFrom( | ||
| this.httpService.post('https://api.affinity.co/oauth/token', { | ||
| grant_type: 'refresh_token', | ||
| refresh_token: connection.refreshToken, | ||
| client_id: this.env.get('AFFINITY_CLIENT_ID'), | ||
| client_secret: this.env.get('AFFINITY_CLIENT_SECRET'), | ||
| }) | ||
| ); | ||
|
|
||
| const { access_token, refresh_token, expires_in } = refreshResponse.data; | ||
|
|
||
| await this.prisma.connection.update({ | ||
| where: { id: connection.id }, | ||
| data: { | ||
| accessToken: access_token, | ||
| refreshToken: refresh_token, | ||
| expiresAt: new Date(Date.now() + expires_in * 1000), | ||
| }, | ||
| }); | ||
|
|
||
| return access_token; | ||
| } | ||
|
|
||
| return connection.accessToken; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { Address } from '@crm/@lib/@types'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a newline at the end of the file. |
||
| import { | ||
| UnifiedContactInput, | ||
| UnifiedContactOutput, | ||
| } from '@crm/contact/types/model.unified'; | ||
| import { IContactMapper } from '@crm/contact/types'; | ||
| import { Utils } from '@crm/@lib/@utils'; | ||
| import { AffinityContactInput, AffinityContactOutput } from './types'; | ||
|
|
||
| export class AffinityMapper implements IContactMapper { | ||
| desunify( | ||
| source: UnifiedContactInput, | ||
| customFieldMappings?: { | ||
| slug: string; | ||
| remote_id: string; | ||
| }[], | ||
| ): AffinityContactInput { | ||
| // Mapping from unified contact to Affinity contact | ||
| return { | ||
| firstName: source.firstName, | ||
| lastName: source.lastName, | ||
| email: source.email, | ||
| phone: source.phone, | ||
| company: source.company, | ||
| }; | ||
| } | ||
|
|
||
| unify( | ||
| source: AffinityContactOutput | AffinityContactOutput[], | ||
| customFieldMappings?: { | ||
| slug: string; | ||
| remote_id: string; | ||
| }[], | ||
| ): UnifiedContactOutput | UnifiedContactOutput[] { | ||
| if (Array.isArray(source)) { | ||
|
Comment on lines
+21
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential null or undefined values in |
||
| return source.map(contact => this.unify(contact)); | ||
| } | ||
|
|
||
| return { | ||
| id: source.id, | ||
| firstName: source.firstName, | ||
| lastName: source.lastName, | ||
| email: source.email, | ||
| phone: source.phone, | ||
| company: source.company, | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export interface AffinityContact { | ||
| id: string; | ||
| firstName: string; | ||
| lastName: string; | ||
| email: string; | ||
| phone?: string; | ||
| company?: string; | ||
| } | ||
|
|
||
| export type AffinityContactInput = Partial<AffinityContact>; | ||
| export type AffinityContactOutput = AffinityContact; | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a newline at the end of the file. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,43 @@ | ||
| import { Injectable } from '@nestjs/common'; | ||
| import { IContactService } from '@crm/contact/types'; | ||
| import { CrmObject } from '@crm/@lib/@types'; | ||
| import axios from 'axios'; | ||
| import { PrismaService } from '@@core/prisma/prisma.service'; | ||
| import { LoggerService } from '@@core/logger/logger.service'; | ||
| import { ActionType, handleServiceError } from '@@core/utils/errors'; | ||
| import { EncryptionService } from '@@core/encryption/encryption.service'; | ||
| import { ApiResponse } from '@@core/utils/types'; | ||
| import { IContactService, ApiResponse, DesunifyReturnType } from '../../types'; | ||
| import { PrismaService } from '../../../prisma/prisma.service'; | ||
| import { LoggerService } from '../../../logger/logger.service'; | ||
| import { EncryptionService } from '../../../encryption/encryption.service'; | ||
| import { ServiceRegistry } from '../../../registry/service.registry'; | ||
| import { AffinityContactInput, AffinityContactOutput } from './types'; | ||
|
|
||
| @Injectable() | ||
| export class AffinityService implements IContactService { | ||
| constructor( | ||
| private prisma: PrismaService, | ||
| private logger: LoggerService, | ||
| private cryptoService: EncryptionService, | ||
| private registry: ServiceRegistry, | ||
| ) { | ||
| this.logger.setContext( | ||
| CrmObject.contact.toUpperCase() + ':' + AffinityService.name, | ||
| ); | ||
| this.registry.registerService('affinity', this); | ||
| } | ||
|
|
||
| async addContact( | ||
| contactData: AffinityContactInput, | ||
| linkedUserId: string, | ||
| ): Promise<ApiResponse<AffinityContactOutput>> { | ||
| // Implementation for adding a contact to Affinity CRM | ||
| // This should interact with Affinity CRM's API to add a contact | ||
| return; | ||
| } | ||
|
Comment on lines
+23
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Please implement the interaction with Affinity CRM's API or provide a stub if this is a work in progress. |
||
|
|
||
| async syncContacts( | ||
| linkedUserId: string, | ||
| ): Promise<ApiResponse<AffinityContactOutput[]>> { | ||
| // Implementation for syncing contacts from Affinity CRM | ||
| // This should interact with Affinity CRM's API to fetch contacts | ||
| return; | ||
| } | ||
|
developerdhruv marked this conversation as resolved.
|
||
| } | ||
|
|
||
| import { ServiceRegistry } from '../registry.service'; | ||
| import { AttioContactInput, AttioContactOutput } from './types'; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the version of
nestjs. The version^0.0.1seems incorrect, possibly a typo or a placeholder.