diff --git a/apps/backend/src/app/api/v1/team-memberships/crud.tsx b/apps/backend/src/app/api/v1/team-memberships/crud.tsx index f63efb1cc..f1dc2f2e6 100644 --- a/apps/backend/src/app/api/v1/team-memberships/crud.tsx +++ b/apps/backend/src/app/api/v1/team-memberships/crud.tsx @@ -10,6 +10,7 @@ import { KnownErrors } from "@stackframe/stack-shared"; import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; import { PrismaTransaction } from "@/lib/types"; import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies"; +import { sendTeamMembershipCreatedWebhook, sendTeamMembershipDeletedWebhook } from "@/lib/webhooks"; export async function addUserToTeam(tx: PrismaTransaction, options: { @@ -91,12 +92,22 @@ export const teamMembershipsCrudHandlers = createLazyProxy(() => createCrudHandl }); }); - return {}; + const data = { + team_id: params.team_id, + user_id: userId, + }; + + await sendTeamMembershipCreatedWebhook({ + projectId: auth.project.id, + data, + }); + + return data; }, onDelete: async ({ auth, params }) => { - await prismaClient.$transaction(async (tx) => { - const userId = getIdFromUserIdOrMe(params.user_id, auth.user); + const userId = getIdFromUserIdOrMe(params.user_id, auth.user); + await prismaClient.$transaction(async (tx) => { // Users are always allowed to remove themselves from a team // Only users with the $remove_members permission can remove other users if (auth.type === 'client') { @@ -129,5 +140,13 @@ export const teamMembershipsCrudHandlers = createLazyProxy(() => createCrudHandl }, }); }); + + await sendTeamMembershipDeletedWebhook({ + projectId: auth.project.id, + data: { + team_id: params.team_id, + user_id: userId, + }, + }); }, })); diff --git a/apps/backend/src/app/api/v1/users/crud.tsx b/apps/backend/src/app/api/v1/users/crud.tsx index d03595fd2..c538b7f5c 100644 --- a/apps/backend/src/app/api/v1/users/crud.tsx +++ b/apps/backend/src/app/api/v1/users/crud.tsx @@ -541,7 +541,6 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC }); } - await sendUserCreatedWebhook({ projectId: auth.project.id, data: result, diff --git a/apps/backend/src/lib/openapi.tsx b/apps/backend/src/lib/openapi.tsx index 57429f898..45337da17 100644 --- a/apps/backend/src/lib/openapi.tsx +++ b/apps/backend/src/lib/openapi.tsx @@ -57,7 +57,10 @@ export function parseWebhookOpenAPI(options: { metadata: webhook.metadata, method: 'POST', path: webhook.type, - requestBodyDesc: undefinedIfMixed(webhook.schema.describe()) || yupObject().describe(), + requestBodyDesc: undefinedIfMixed(yupObject({ + type: yupString().required().meta({ openapiField: { description: webhook.type, exampleValue: webhook.type }}), + data: webhook.schema.required(), + }).describe()) || yupObject().describe(), responseTypeDesc: yupString().oneOf(['json']).describe(), statusCodeDesc: yupNumber().oneOf([200]).describe(), }), diff --git a/apps/backend/src/lib/webhooks.tsx b/apps/backend/src/lib/webhooks.tsx index 61de29b0d..1aad8d59b 100644 --- a/apps/backend/src/lib/webhooks.tsx +++ b/apps/backend/src/lib/webhooks.tsx @@ -1,3 +1,4 @@ +import { teamMembershipCreatedWebhookEvent, teamMembershipDeletedWebhookEvent } from "@stackframe/stack-shared/dist/interface/crud/team-memberships"; import { teamCreatedWebhookEvent, teamDeletedWebhookEvent, teamUpdatedWebhookEvent } from "@stackframe/stack-shared/dist/interface/crud/teams"; import { userCreatedWebhookEvent, userDeletedWebhookEvent, userUpdatedWebhookEvent } from "@stackframe/stack-shared/dist/interface/crud/users"; import { WebhookEvent } from "@stackframe/stack-shared/dist/interface/webhooks"; @@ -51,3 +52,5 @@ export const sendUserDeletedWebhook = createWebhookSender(userDeletedWebhookEven export const sendTeamCreatedWebhook = createWebhookSender(teamCreatedWebhookEvent); export const sendTeamUpdatedWebhook = createWebhookSender(teamUpdatedWebhookEvent); export const sendTeamDeletedWebhook = createWebhookSender(teamDeletedWebhookEvent); +export const sendTeamMembershipCreatedWebhook = createWebhookSender(teamMembershipCreatedWebhookEvent); +export const sendTeamMembershipDeletedWebhook = createWebhookSender(teamMembershipDeletedWebhookEvent); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts index 7a329c429..e00a187b8 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts @@ -49,7 +49,10 @@ it("creates a team and manage users on the server", async ({ expect }) => { expect(response).toMatchInlineSnapshot(` NiceResponse { "status": 201, - "body": {}, + "body": { + "team_id": "", + "user_id": "", + }, "headers": Headers {