Skip to content

Commit

Permalink
Neon webhook create (#368)
Browse files Browse the repository at this point in the history
  • Loading branch information
fomalhautb authored Dec 12, 2024
1 parent 6ecbdab commit e07142f
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 7 deletions.
42 changes: 42 additions & 0 deletions apps/backend/src/app/api/v1/integrations/neon/webhooks/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getSvixClient } from "@/lib/webhooks";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { adaptSchema, neonAuthorizationHeaderSchema, urlSchema, yupNumber, yupObject, yupString, yupTuple } from "@stackframe/stack-shared/dist/schema-fields";

export const POST = createSmartRouteHandler({
metadata: {
hidden: true,
},
request: yupObject({
auth: yupObject({
project: adaptSchema.defined(),
}).defined(),
body: yupObject({
url: urlSchema.defined(),
description: yupString().optional(),
}).defined(),
headers: yupObject({
authorization: yupTuple([neonAuthorizationHeaderSchema.defined()]).defined(),
}).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
secret: yupString().defined(),
}).defined(),
}),
handler: async ({ auth, body }) => {
const svix = getSvixClient();
await svix.application.getOrCreate({ uid: auth.project.id, name: auth.project.id });
const endpoint = await svix.endpoint.create(auth.project.id, { url: body.url, description: body.description });
const secret = await svix.endpoint.getSecret(auth.project.id, endpoint.id);

return {
statusCode: 200,
bodyType: "json",
body: {
secret: secret.key,
},
};
},
});
8 changes: 2 additions & 6 deletions apps/backend/src/app/api/v1/webhooks/svix-token/route.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { getSvixClient } from "@/lib/webhooks";
import { createCrudHandlers } from "@/route-handlers/crud-handler";
import { svixTokenCrud } from "@stackframe/stack-shared/dist/interface/crud/svix-token";
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
import { Svix } from "svix";

const appPortalCrudHandlers = createLazyProxy(() => createCrudHandlers(svixTokenCrud, {
paramsSchema: yupObject({}),
onCreate: async ({ auth }) => {
const svix = new Svix(
getEnvVariable("STACK_SVIX_API_KEY"),
{ serverUrl: getEnvVariable("STACK_SVIX_SERVER_URL", "") || undefined }
);
const svix = getSvixClient();
await svix.application.getOrCreate({ uid: auth.project.id, name: auth.project.id });
const result = await svix.authentication.appPortalAccess(auth.project.id, {});
return { token: result.token };
Expand Down
7 changes: 7 additions & 0 deletions apps/backend/src/lib/webhooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { Svix } from "svix";
import * as yup from "yup";

export function getSvixClient() {
return new Svix(
getEnvVariable("STACK_SVIX_API_KEY"),
{ serverUrl: getEnvVariable("STACK_SVIX_SERVER_URL", "") || undefined }
);
}

async function sendWebhooks(options: {
type: string,
projectId: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { it } from "../../../../../../helpers";
import { niceBackendFetch } from "../../../../../backend-helpers";
import { provisionProject } from "./projects/provision.test";


it("should be able to create a webhook", async ({ expect }) => {
await provisionProject();
const response = await niceBackendFetch("/api/v1/integrations/neon/webhooks", {
method: "POST",
body: {
url: "https://example.com/neon",
description: "Test webhook",
},
headers: {
"Authorization": "Basic bmVvbi1sb2NhbDpuZW9uLWxvY2FsLXNlY3JldA==",
},
accessType: "admin",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": { "secret": <stripped field 'secret'> },
"headers": Headers { <some fields may have been hidden> },
}
`);
});
1 change: 1 addition & 0 deletions apps/e2e/tests/snapshot-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const stripFields = [
"attempt_code",
"nonce",
"authorization_code",
"secret",
] as const;

const stripFieldsIfString = [
Expand Down
2 changes: 1 addition & 1 deletion eslint-configs/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ module.exports = {
},
{
selector:
"MemberExpression:has(Identifier[name='yupString']) Identifier[name='url']",
"MemberExpression:has(Identifier[name='yupString']) > Identifier[name='url']",
message:
"Use urlSchema from schema-fields.tsx instead of yupString().url().",
},
Expand Down

0 comments on commit e07142f

Please sign in to comment.