The integration service now returns the response in the format the webapp is expecting
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { PostgresCacheService } from "cache/postgresCache";
|
||||
import { AuthCredentials } from "core/authentication/types";
|
||||
import { RequestError } from "core/request/errors";
|
||||
import { Service } from "core/service/types";
|
||||
import { validateInputs } from "core/validation/inputs";
|
||||
import { Request, Response } from "express";
|
||||
import { stat } from "fs";
|
||||
import { catalog } from "integrations/catalog";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -15,6 +18,17 @@ const requestBodySchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
type ReturnResponse = {
|
||||
response: NormalizedResponse;
|
||||
isRetryable: boolean;
|
||||
ok: boolean;
|
||||
};
|
||||
|
||||
type NormalizedResponse = {
|
||||
output: NonNullable<any>;
|
||||
context: any;
|
||||
};
|
||||
|
||||
export async function handleAction(req: Request, res: Response) {
|
||||
const { service, action } = req.params;
|
||||
|
||||
@@ -24,11 +38,13 @@ export async function handleAction(req: Request, res: Response) {
|
||||
|
||||
if (!matchingService) {
|
||||
res.status(404).send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
service,
|
||||
error: { type: "missing_service", message: "Service not found" },
|
||||
})
|
||||
JSON.stringify(
|
||||
error(404, false, {
|
||||
type: "missing_service",
|
||||
message: "Service not found",
|
||||
service,
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -39,12 +55,14 @@ export async function handleAction(req: Request, res: Response) {
|
||||
|
||||
if (!matchingAction) {
|
||||
res.status(404).send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
service,
|
||||
action,
|
||||
error: { type: "missing_action", message: "Action not found" },
|
||||
})
|
||||
JSON.stringify(
|
||||
error(404, false, {
|
||||
type: "missing_action",
|
||||
message: "Action not found",
|
||||
service,
|
||||
action,
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -53,10 +71,15 @@ export async function handleAction(req: Request, res: Response) {
|
||||
|
||||
if (!parsedRequestBody.success) {
|
||||
res.status(400).send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: { type: "invalid_body", issues: parsedRequestBody.error.issues },
|
||||
})
|
||||
JSON.stringify(
|
||||
error(400, false, {
|
||||
type: "invalid_body",
|
||||
message: "Action not found",
|
||||
service,
|
||||
action,
|
||||
issues: parsedRequestBody.error.issues,
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -127,12 +150,9 @@ export async function handleAction(req: Request, res: Response) {
|
||||
}
|
||||
);
|
||||
if (!inputValidationResult.success) {
|
||||
res.status(400).send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: inputValidationResult.error,
|
||||
})
|
||||
);
|
||||
res
|
||||
.status(400)
|
||||
.send(JSON.stringify(error(400, false, inputValidationResult.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,11 +165,60 @@ export async function handleAction(req: Request, res: Response) {
|
||||
cache,
|
||||
metadata
|
||||
);
|
||||
res.send(JSON.stringify(data));
|
||||
|
||||
//convert into the format for the webapp
|
||||
const response: ReturnResponse = {
|
||||
ok: true,
|
||||
isRetryable: isRetryable(matchingService, data.status),
|
||||
response: {
|
||||
output: data.body ?? {},
|
||||
context: {
|
||||
statusCode: data.status,
|
||||
headers: data.headers,
|
||||
},
|
||||
},
|
||||
};
|
||||
res.send(JSON.stringify(response));
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
|
||||
if (e instanceof Error) {
|
||||
res
|
||||
.status(500)
|
||||
.send(JSON.stringify(error(500, false, { error: JSON.stringify(e) })));
|
||||
return;
|
||||
}
|
||||
|
||||
if ("error" in e) {
|
||||
res.status(500).send(JSON.stringify(error(500, false, e.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
res
|
||||
.status(500)
|
||||
.send(JSON.stringify({ success: false, errors: e.toString() }));
|
||||
.send(JSON.stringify(error(500, false, { error: JSON.stringify(e) })));
|
||||
}
|
||||
}
|
||||
|
||||
function isRetryable(service: Service, status: number): boolean {
|
||||
return service.retryableStatusCodes.includes(status);
|
||||
}
|
||||
|
||||
function error(
|
||||
status: number,
|
||||
isRetryable: boolean,
|
||||
error: Record<string, any>
|
||||
): ReturnResponse {
|
||||
const response: ReturnResponse = {
|
||||
ok: false,
|
||||
isRetryable,
|
||||
response: {
|
||||
output: error,
|
||||
context: {
|
||||
statusCode: status,
|
||||
headers: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ const EnvironmentSchema = z.object({
|
||||
PULSAR_AUDIENCE: z.string().optional(),
|
||||
PULSAR_DEBUG: z.string().optional(),
|
||||
INTERNAL_TRIGGER_API_KEY: z.string().optional(),
|
||||
INTEGRATIONS_API_KEY: z.string(),
|
||||
INTEGRATIONS_API_ORIGIN: z.string(),
|
||||
});
|
||||
|
||||
export type Environment = z.infer<typeof EnvironmentSchema>;
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { IntegrationRequest } from "~/models/integrationRequest.server";
|
||||
import { getAccessInfo } from "../accessInfo.server";
|
||||
import { RedisCacheService } from "../cacheService.server";
|
||||
import { getIntegrations } from "~/models/integrations.server";
|
||||
import { env } from "~/env.server";
|
||||
|
||||
type CallResponse =
|
||||
| {
|
||||
@@ -65,7 +66,8 @@ export class PerformIntegrationRequest {
|
||||
accessInfo,
|
||||
integrationRequest,
|
||||
cache,
|
||||
integrationRequest.externalService.workflowId
|
||||
integrationRequest.externalService.workflowId,
|
||||
integrationRequest.externalService.connection.id
|
||||
);
|
||||
|
||||
if (performedRequest.ok) {
|
||||
@@ -224,31 +226,94 @@ export class PerformIntegrationRequest {
|
||||
accessInfo: AccessInfo,
|
||||
integrationRequest: IntegrationRequest,
|
||||
cache: CacheService,
|
||||
workflowId: string
|
||||
workflowId: string,
|
||||
connectionId: string
|
||||
): Promise<PerformedRequestResponse> {
|
||||
const integrationInfo = getIntegrations(true).find(
|
||||
(i) => i.metadata.slug === service
|
||||
);
|
||||
switch (integrationRequest.version) {
|
||||
case "1": {
|
||||
const integrationInfo = getIntegrations(true).find(
|
||||
(i) => i.metadata.slug === service
|
||||
);
|
||||
|
||||
if (!integrationInfo) {
|
||||
throw new Error(`Unknown service: ${service}`);
|
||||
if (!integrationInfo) {
|
||||
throw new Error(`Unknown service: ${service}`);
|
||||
}
|
||||
|
||||
const { requests } = integrationInfo;
|
||||
|
||||
if (!requests) {
|
||||
throw new Error(`Service ${service} does not support requests`);
|
||||
}
|
||||
|
||||
return requests.perform({
|
||||
accessInfo,
|
||||
endpoint: integrationRequest.endpoint,
|
||||
params: integrationRequest.params,
|
||||
cache,
|
||||
metadata: {
|
||||
requestId: integrationRequest.id,
|
||||
workflowId: workflowId,
|
||||
},
|
||||
});
|
||||
}
|
||||
case "2": {
|
||||
let credentials: { accessToken: string } | undefined = undefined;
|
||||
switch (accessInfo.type) {
|
||||
case "oauth2":
|
||||
credentials = {
|
||||
accessToken: accessInfo.accessToken,
|
||||
};
|
||||
break;
|
||||
case "api_key":
|
||||
credentials = {
|
||||
accessToken: accessInfo.api_key,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${env.INTEGRATIONS_API_ORIGIN}/api/v1/${service}/action/${integrationRequest.endpoint}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${env.INTEGRATIONS_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
credentials,
|
||||
params: integrationRequest.params,
|
||||
metadata: {
|
||||
requestId: integrationRequest.id,
|
||||
workflowId: workflowId,
|
||||
connectionId,
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const json = await response.json();
|
||||
return json;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
ok: false,
|
||||
isRetryable: true,
|
||||
response: {
|
||||
output: {
|
||||
error: {
|
||||
message: JSON.stringify(e),
|
||||
},
|
||||
},
|
||||
context: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
`Unknown integration request version: ${integrationRequest.version}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { requests } = integrationInfo;
|
||||
|
||||
if (!requests) {
|
||||
throw new Error(`Service ${service} does not support requests`);
|
||||
}
|
||||
|
||||
return requests.perform({
|
||||
accessInfo,
|
||||
endpoint: integrationRequest.endpoint,
|
||||
params: integrationRequest.params,
|
||||
cache,
|
||||
metadata: {
|
||||
requestId: integrationRequest.id,
|
||||
workflowId: workflowId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user