Skip to content

Commit

Permalink
Nango QBO POC complete
Browse files Browse the repository at this point in the history
With ability to sync QBO data using creds from Nango
  • Loading branch information
tonyxiao committed Oct 13, 2023
1 parent c6312ed commit 3731d2a
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 16 deletions.
1 change: 1 addition & 0 deletions integrations/integration-qbo/QBOClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const zConfig = z.object({
clientId: z.string(),
clientSecret: z.string(),
scope: z.string(),
envName: z.enum(['sandbox', 'production']),
url: z.string().nullish().describe('For proxies, not typically needed'),
verifierToken: z.string().nullish().describe('For webhooks'),
})
Expand Down
15 changes: 12 additions & 3 deletions packages/cdk-core/NangoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,12 @@ export const zConnection = zConnectionShort.extend({
raw: z.object({
access_token: z.string(),
expires_in: z.number(),
refresh_token_expires_in: z.number(),
expires_at: z.string().datetime(),
/** Refresh token (Only returned if the REFRESH_TOKEN boolean parameter is set to true and the refresh token is available) */
refresh_token: z.string().nullish(),
refresh_token_expires_in: z.number().nullish(),
token_type: z.string(), //'bearer',
scope: z.string(),
expires_at: z.string().datetime(),
}),
}),
connection_config: z.record(z.unknown()),
Expand All @@ -192,6 +194,7 @@ export const zConnection = zConnectionShort.extend({
export const zIntegration = zIntegrationShort.extend({
client_id: z.string(),
client_secret: z.string(),
/** comma deliminated scopes with no spaces in between */
scopes: z.string(),
app_link: z.string().nullish(),
// In practice we only use nango for oauth integrations
Expand All @@ -214,6 +217,8 @@ export const zUpsertIntegration = zIntegration
})
.partial({auth_mode: true})

export type UpsertIntegration = z.infer<typeof zUpsertIntegration>

export const endpoints = {
get: {
'/config': {input: {}, output: z.array(zIntegrationShort)},
Expand Down Expand Up @@ -251,7 +256,11 @@ export const endpoints = {
'/connection/{connection_id}': {
input: {
path: z.object({connection_id: z.string()}),
query: z.object({provider_config_key: z.string()}),
query: z.object({
provider_config_key: z.string(),
force_refresh: z.boolean().optional(),
refresh_token: z.boolean().optional(),
}),
},
output: z.undefined(),
},
Expand Down
29 changes: 19 additions & 10 deletions packages/engine-backend/router/adminRouter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {TRPCError} from '@trpc/server'

import type {
UpsertIntegration} from '@usevenice/cdk-core';
import {
extractProviderName,
handlersLink,
Expand All @@ -9,7 +11,7 @@ import {
zId,
zRaw,
} from '@usevenice/cdk-core'
import {makeUlid, rxjs, z} from '@usevenice/util'
import {HTTPError, makeUlid, rxjs, z} from '@usevenice/util'

import {adminProcedure, trpc} from './_base'

Expand Down Expand Up @@ -115,15 +117,22 @@ export const adminRouter = trpc.router({
if (provider.metadata?.nangoProvider) {
// TODO: Should we use put vs. post? need to fix it up here...
// Create nango integration here...
await ctx.nango.put('/config', {
bodyJson: {
provider_config_key: id,
provider: provider.metadata.nangoProvider,
// TODO: gotta fix the typing here...
oauth_client_id: (input.config as any).clientId,
oauth_client_secret: (input.config as any).clientSecret,
oauth_scopes: (input.config as any).scope,
},
const bodyJson: UpsertIntegration = {
provider_config_key: id,
provider: provider.metadata.nangoProvider,
// TODO: gotta fix the typing here...
oauth_client_id: (input.config as any).clientId,
oauth_client_secret: (input.config as any).clientSecret,
oauth_scopes: (input.config as any).scope,
}
await ctx.nango.put('/config', {bodyJson}).catch((err) => {
if (
err instanceof HTTPError &&
err.response?.data.type === 'unknown_provider_config'
) {
return ctx.nango.post('/config', {bodyJson})
}
throw err
})
}

Expand Down
10 changes: 8 additions & 2 deletions packages/engine-backend/router/endUserRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,18 @@ export const endUserRouter = trpc.router({
.parse(input)
const result = await ctx.nango.get('/connection/{connectionId}', {
path: {connectionId: resoId},
query: {provider_config_key: intId},
query: {provider_config_key: intId, refresh_token: true},
})

return {
resourceExternalId: extractId(resoId)[2],
settings: {nango: result},
settings: {
nango: result,
accessToken: result.credentials.access_token,
accessTokenExpiresAt: result.credentials.expires_at,
refreshToken: result.credentials.raw.refresh_token,
realmId: result.connection_config['realmId'],
},
} satisfies Omit<ResourceUpdate<any, any>, 'endUserId'>
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/domain-components/ProviderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const IntegrationCard = ({
// Temporary hack due to presence of labels for plaid. Need better design for ProviderCard and IntegrationCard
labels={
// TODO: Fix this hack soon. We should have some kind of mapStandardIntegration method
int.providerName === 'plaid' && int.config?.['envName']
int.config?.['envName']
? // eslint-disable-next-line @typescript-eslint/no-base-to-string
[`${int.config?.['envName']}`]
: []
Expand Down

0 comments on commit 3731d2a

Please sign in to comment.