Skip to content

Commit

Permalink
Finish Netlify integration v1 full-loop
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Dec 14, 2022
1 parent fe17d84 commit 787e54f
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 108 deletions.
2 changes: 1 addition & 1 deletion backend/src/controllers/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
let apps;
try {
apps = await getApps({
integration: req.integrationAuth.integration,
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
} catch (err) {
Expand Down
15 changes: 12 additions & 3 deletions backend/src/controllers/integrationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ export const updateIntegration = async (req: Request, res: Response) => {
// integration has the correct fields populated in [Integration]

try {
const { app, environment, isActive, target } = req.body;

const {
app,
environment,
isActive,
target, // vercel-specific integration param
context, // netlify-specific integration param
siteId // netlify-specific integration param
} = req.body;

integration = await Integration.findOneAndUpdate(
{
_id: req.integration._id
Expand All @@ -45,7 +52,9 @@ export const updateIntegration = async (req: Request, res: Response) => {
environment,
isActive,
app,
target
target,
context,
siteId
},
{
new: true
Expand Down
1 change: 1 addition & 0 deletions backend/src/helpers/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const getSecretsHelper = async ({
const key = await getKey({ workspaceId });
const secrets = await Secret.find({
workspaceId,
environment,
type: SECRET_SHARED
});

Expand Down
23 changes: 14 additions & 9 deletions backend/src/helpers/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { BotService, IntegrationService } from '../services';
import {
ENV_DEV,
EVENT_PUSH_SECRETS,
INTEGRATION_VERCEL
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
} from '../variables';

interface Update {
workspace: string;
integration: string;
teamId?: string;
accountId?: string;
}

/**
Expand Down Expand Up @@ -55,8 +57,6 @@ const handleOAuthExchangeHelper = async ({
integration,
code
});

return;

let update: Update = {
workspace: workspaceId,
Expand All @@ -67,6 +67,9 @@ const handleOAuthExchangeHelper = async ({
case INTEGRATION_VERCEL:
update.teamId = res.teamId;
break;
case INTEGRATION_NETLIFY:
update.accountId = res.accountId;
break;
}

integrationAuth = await IntegrationAuth.findOneAndUpdate({
Expand Down Expand Up @@ -124,11 +127,12 @@ const syncIntegrationsHelper = async ({
}) => {
let integrations;
try {

integrations = await Integration.find({
workspace: workspaceId,
isActive: true,
app: { $ne: null }
}).populate<{integrationAuth: IIntegrationAuth}>('integrationAuth', 'accessToken');
});

// for each workspace integration, sync/push secrets
// to that integration
Expand All @@ -139,22 +143,23 @@ const syncIntegrationsHelper = async ({
environment: integration.environment
});

const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error('Failed to find integration auth');

// get integration auth access token
const accessToken = await getIntegrationAuthAccessHelper({
integrationAuthId: integration.integrationAuth._id.toString()
integrationAuthId: integration.integrationAuth.toString()
});

// sync secrets to integration
await syncSecrets({
integration: integration.integration,
app: integration.app,
target: integration.target,
integration,
integrationAuth,
secrets,
accessToken
});
}
} catch (err) {
console.log('syncIntegrationsHelper error', err);
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to integrations');
Expand Down
69 changes: 61 additions & 8 deletions backend/src/integrations/apps.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import {
IIntegrationAuth
} from '../models';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL
} from '../variables';

/**
Expand All @@ -16,15 +21,21 @@ import {
* @returns {String} apps.name - name of integration app
*/
const getApps = async ({
integration,
integrationAuth,
accessToken
}: {
integration: string;
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let apps;

interface App {
name: string;
siteId?: string;
}

let apps: App[]; // TODO: add type and define payloads for apps
try {
switch (integration) {
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken
Expand All @@ -35,6 +46,12 @@ const getApps = async ({
accessToken
});
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
integrationAuth,
accessToken
});
break;
}

} catch (err) {
Expand Down Expand Up @@ -82,9 +99,9 @@ const getAppsHeroku = async ({
/**
* Return list of names of apps for Vercel integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Heroku API
* @returns {Object[]} apps - names of Heroku apps
* @returns {String} apps.name - name of Heroku app
* @param {String} obj.accessToken - access token for Vercel API
* @returns {Object[]} apps - names of Vercel apps
* @returns {String} apps.name - name of Vercel app
*/
const getAppsVercel = async ({
accessToken
Expand All @@ -111,6 +128,42 @@ const getAppsVercel = async ({
return apps;
}

/**
* Return list of names of sites for Netlify integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Netlify API
* @returns {Object[]} apps - names of Netlify sites
* @returns {String} apps.name - name of Netlify site
*/
const getAppsNetlify = async ({
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let apps;
try {
const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})).data;

apps = res.map((a: any) => ({
name: a.name,
siteId: a.site_id
}));

} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Netlify integration apps');
}

return apps;
}

export {
getApps
}
57 changes: 36 additions & 21 deletions backend/src/integrations/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ interface ExchangeCodeVercelResponse {
team_id?: string;
}

interface ExchangeCodeNetlifyResponse {
access_token: string;
token_type: string;
refresh_token: string;
scope: string;
created_at: number;
}

/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
* code-token exchange for integration named [integration]
Expand Down Expand Up @@ -152,7 +160,6 @@ const exchangeCodeVercel = async ({
redirect_uri: `${SITE_URL}/vercel`
} as any)
)).data;

} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
Expand Down Expand Up @@ -182,42 +189,50 @@ const exchangeCodeNetlify = async ({
}: {
code: string;
}) => {
console.log('exchangeCodeNetlify');
let res: ExchangeCodeVercelResponse;
let res: ExchangeCodeNetlifyResponse;
let accountId;
try {
res = (await axios.post(
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: CLIENT_ID_VERCEL,
client_secret: CLIENT_SECRET_VERCEL,
redirect_uri: `${SITE_URL}/vercel`
client_id: CLIENT_ID_NETLIFY,
client_secret: CLIENT_SECRET_NETLIFY,
redirect_uri: `${SITE_URL}/netlify`
} as any)
)).data;

res = (await axios.post(
INTEGRATION_NETLIFY_TOKEN_URL,
`${"https://api.netlify.com/oauth/token"}?code=${code}&client_id=${CLIENT_ID_NETLIFY}&client_secret=${CLIENT_SECRET_NETLIFY}&grant_type=authorization_code&redirect_uri=${SITE_URL}/netlify`
// INTEGRATION_NETLIFY_TOKEN_URL,
// new URLSearchParams({
// code: code,
// client_id: CLIENT_ID_NETLIFY,
// client_secret: CLIENT_SECRET_NETLIFY,
// redirect_uri: `${SITE_URL}/netlify`
// } as any)
));
const res2 = await axios.get(
'https://api.netlify.com/api/v1/sites',
{
headers: {
Authorization: `Bearer ${res.access_token}`
}
}
);

console.log('resss', res);
const res3 = (await axios.get(
'https://api.netlify.com/api/v1/accounts',
{
headers: {
Authorization: `Bearer ${res.access_token}`
}
}
)).data;

accountId = res3[0].id;

} catch (err) {
console.error('netlify err', err);
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Netlify');
}

return ({

accessToken: res.access_token,
refreshToken: res.refresh_token,
accountId
});
}

Expand Down
Loading

0 comments on commit 787e54f

Please sign in to comment.