Skip to content

Commit

Permalink
refactor(core,schemas,test): rename DataHook data update event name (#…
Browse files Browse the repository at this point in the history
…5876)

rename the DataHook Schema data update event name
  • Loading branch information
simeng-li authored May 16, 2024
1 parent c558aff commit 5e7bee1
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ describe('submit action', () => {
login: { accountId: 'foo' },
});
expect(ctx.assignDataHookContext).toBeCalledWith({
event: 'User.Updated',
event: 'User.Data.Updated',
user: updateProfile,
});
});
Expand Down Expand Up @@ -433,7 +433,7 @@ describe('submit action', () => {
login: { accountId: 'foo' },
});
expect(ctx.assignDataHookContext).toBeCalledWith({
event: 'User.Updated',
event: 'User.Data.Updated',
user: {
primaryEmail: 'email',
name: userInfo.name,
Expand All @@ -459,7 +459,7 @@ describe('submit action', () => {
});
expect(assignInteractionResults).not.toBeCalled();
expect(ctx.assignDataHookContext).toBeCalledWith({
event: 'User.Updated',
event: 'User.Data.Updated',
user: {
passwordEncrypted: 'passwordEncrypted',
passwordEncryptionMethod: 'plain',
Expand Down
24 changes: 18 additions & 6 deletions packages/core/src/routes/interaction/actions/submit-interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ async function handleSubmitRegister(
// If it's Logto Cloud, Check if the new user has any pending invitations, if yes, skip onboarding flow.
const invitations =
isCloud && userProfile.primaryEmail
? await organizations.invitations.findEntities({ invitee: userProfile.primaryEmail })
? await organizations.invitations.findEntities({
invitee: userProfile.primaryEmail,
})
: [];
const hasPendingInvitations = invitations.some(
(invitation) => invitation.status === OrganizationInvitationStatus.Pending
Expand Down Expand Up @@ -189,7 +191,9 @@ async function handleSubmitRegister(
ctx.assignDataHookContext({ event: 'User.Created', user });

log?.append({ userId: id });
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.Register) });
appInsights.client?.trackEvent({
name: getEventName(Component.Core, CoreEvent.Register),
});

void trySafe(postAffiliateLogs(ctx, cloudConnection, id, tenantId), (error) => {
getConsoleLogFromContext(ctx).warn('Failed to post affiliate logs', error);
Expand Down Expand Up @@ -238,10 +242,15 @@ async function handleSubmitSignIn(
ctx.assignInteractionHookResult({ userId: accountId });
// Trigger user.updated data hook event if the user profile or mfa data is updated
if (hasUpdatedProfile(updateUserProfile) || mfaVerifications.length > 0) {
ctx.assignDataHookContext({ event: 'User.Updated', user: updatedUser });
ctx.assignDataHookContext({
event: 'User.Data.Updated',
user: updatedUser,
});
}

appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.SignIn) });
appInsights.client?.trackEvent({
name: getEventName(Component.Core, CoreEvent.SignIn),
});
}

export default async function submitInteraction(
Expand Down Expand Up @@ -270,9 +279,12 @@ export default async function submitInteraction(
profile.password
);

const user = await updateUserById(accountId, { passwordEncrypted, passwordEncryptionMethod });
const user = await updateUserById(accountId, {
passwordEncrypted,
passwordEncryptionMethod,
});
ctx.assignInteractionHookResult({ userId: accountId });
ctx.assignDataHookContext({ event: 'User.Updated', user });
ctx.assignDataHookContext({ event: 'User.Data.Updated', user });

await clearInteractionStorage(ctx, provider);
ctx.status = 204;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('interaction api trigger hooks', () => {
});

// Assert user updated event is not triggered
await assertHookLogResult(dataHook, 'User.Updated', {
await assertHookLogResult(dataHook, 'User.Data.Updated', {
toBeUndefined: true,
});

Expand Down Expand Up @@ -221,7 +221,7 @@ describe('interaction api trigger hooks', () => {
toBeUndefined: true,
});

await assertHookLogResult(dataHook, 'User.Updated', {
await assertHookLogResult(dataHook, 'User.Data.Updated', {
toBeUndefined: true,
});
});
Expand Down Expand Up @@ -257,9 +257,9 @@ describe('interaction api trigger hooks', () => {
toBeUndefined: true,
});

await assertHookLogResult(dataHook, 'User.Updated', {
await assertHookLogResult(dataHook, 'User.Data.Updated', {
hookPayload: {
event: 'User.Updated',
event: 'User.Data.Updated',
interactionEvent: InteractionEvent.SignIn,
sessionId: expect.any(String),
data: expect.objectContaining({ id: user.id, username, primaryEmail: email }),
Expand All @@ -286,9 +286,9 @@ describe('interaction api trigger hooks', () => {
hookPayload: interactionHookEventPayload,
});

await assertHookLogResult(dataHook, 'User.Updated', {
await assertHookLogResult(dataHook, 'User.Data.Updated', {
hookPayload: {
event: 'User.Updated',
event: 'User.Data.Updated',
interactionEvent: InteractionEvent.ForgotPassword,
sessionId: expect.any(String),
data: expect.objectContaining({ id: user.id, username, primaryEmail: email }),
Expand Down
18 changes: 9 additions & 9 deletions packages/integration-tests/src/tests/api/hook/test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ type TestCase = {
export const userDataHookTestCases: TestCase[] = [
{
route: 'PATCH /users/:userId',
event: 'User.Updated',
event: 'User.Data.Updated',
method: 'patch',
endpoint: `users/{userId}`,
payload: { name: 'new name' },
},
{
route: 'PATCH /users/:userId/custom-data',
event: 'User.Updated',
event: 'User.Data.Updated',
method: 'patch',
endpoint: `users/{userId}/custom-data`,
payload: { customData: { foo: 'bar' } },
},
{
route: 'PATCH /users/:userId/profile',
event: 'User.Updated',
event: 'User.Data.Updated',
method: 'patch',
endpoint: `users/{userId}/profile`,
payload: { profile: { nickname: 'darcy' } },
},
{
route: 'PATCH /users/:userId/password',
event: 'User.Updated',
event: 'User.Data.Updated',
method: 'patch',
endpoint: `users/{userId}/password`,
payload: { password: 'new-password' },
Expand All @@ -56,7 +56,7 @@ export const userDataHookTestCases: TestCase[] = [
export const roleDataHookTestCases: TestCase[] = [
{
route: 'PATCH /roles/:id',
event: 'Role.Updated',
event: 'Role.Data.Updated',
method: 'patch',
endpoint: `roles/{roleId}`,
payload: { name: 'new name' },
Expand Down Expand Up @@ -87,7 +87,7 @@ export const roleDataHookTestCases: TestCase[] = [
export const scopesDataHookTestCases: TestCase[] = [
{
route: 'PATCH /resources/:resourceId/scopes/:scopeId',
event: 'Scope.Updated',
event: 'Scope.Data.Updated',
method: 'patch',
endpoint: `resources/{resourceId}/scopes/{scopeId}`,
payload: { name: generateName() },
Expand All @@ -104,7 +104,7 @@ export const scopesDataHookTestCases: TestCase[] = [
export const organizationDataHookTestCases: TestCase[] = [
{
route: 'PATCH /organizations/:id',
event: 'Organization.Updated',
event: 'Organization.Data.Updated',
method: 'patch',
endpoint: `organizations/{organizationId}`,
payload: { description: 'new org description' },
Expand Down Expand Up @@ -142,7 +142,7 @@ export const organizationDataHookTestCases: TestCase[] = [
export const organizationScopeDataHookTestCases: TestCase[] = [
{
route: 'PATCH /organization-scopes/:id',
event: 'OrganizationScope.Updated',
event: 'OrganizationScope.Data.Updated',
method: 'patch',
endpoint: `organization-scopes/{organizationScopeId}`,
payload: { description: 'new org scope description' },
Expand All @@ -159,7 +159,7 @@ export const organizationScopeDataHookTestCases: TestCase[] = [
export const organizationRoleDataHookTestCases: TestCase[] = [
{
route: 'PATCH /organization-roles/:id',
event: 'OrganizationRole.Updated',
event: 'OrganizationRole.Data.Updated',
method: 'patch',
endpoint: `organization-roles/{organizationRoleId}`,
payload: { name: generateName() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type Page } from 'puppeteer';
export const expectToCreateWebhook = async (page: Page) => {
await expect(page).toClick('div[class$=main] div[class$=headline] > button');
await expect(page).toClick('span[class$=label]', { text: 'PostRegister' });
await expect(page).toClick('span[class$=label]', { text: 'User.Updated' });
await expect(page).toClick('span[class$=label]', { text: 'User.Data.Updated' });
await expect(page).toFill('input[name=name]', 'hook_name');
await expect(page).toFill('input[name=url]', 'https://localhost/webhook');
await expect(page).toClick('button[type=submit]');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { sql } from '@silverhand/slonik';

import type { AlterationScript } from '../lib/types/alteration.js';

enum DataHookSchema {
User = 'User',
Role = 'Role',
Scope = 'Scope',
Organization = 'Organization',
OrganizationRole = 'OrganizationRole',
OrganizationScope = 'OrganizationScope',
}

type OldSchemaUpdateEvent = `${DataHookSchema}.${'Updated'}`;
type NewSchemaUpdateEvent = `${DataHookSchema}.Data.Updated`;

const oldSchemaUpdateEvents = Object.freeze([
'User.Updated',
'Role.Updated',
'Scope.Updated',
'Organization.Updated',
'OrganizationRole.Updated',
'OrganizationScope.Updated',
] satisfies OldSchemaUpdateEvent[]);

const newSchemaUpdateEvents = Object.freeze([
'User.Data.Updated',
'Role.Data.Updated',
'Scope.Data.Updated',
'Organization.Data.Updated',
'OrganizationRole.Data.Updated',
'OrganizationScope.Data.Updated',
] as const satisfies NewSchemaUpdateEvent[]);

const updateMap: { [key in OldSchemaUpdateEvent]: NewSchemaUpdateEvent } = {
'User.Updated': 'User.Data.Updated',
'Role.Updated': 'Role.Data.Updated',
'Scope.Updated': 'Scope.Data.Updated',
'Organization.Updated': 'Organization.Data.Updated',
'OrganizationRole.Updated': 'OrganizationRole.Data.Updated',
'OrganizationScope.Updated': 'OrganizationScope.Data.Updated',
};

const reverseMap: { [key in NewSchemaUpdateEvent]: OldSchemaUpdateEvent } = {
'User.Data.Updated': 'User.Updated',
'Role.Data.Updated': 'Role.Updated',
'Scope.Data.Updated': 'Scope.Updated',
'Organization.Data.Updated': 'Organization.Updated',
'OrganizationRole.Data.Updated': 'OrganizationRole.Updated',
'OrganizationScope.Data.Updated': 'OrganizationScope.Updated',
};

// This alteration script filters all the hook's events jsonb column to replace all the old schema update events with the new schema update events.

const isOldSchemaUpdateEvent = (event: string): event is OldSchemaUpdateEvent =>
// eslint-disable-next-line no-restricted-syntax
oldSchemaUpdateEvents.includes(event as OldSchemaUpdateEvent);

const isNewSchemaUpdateEvent = (event: string): event is NewSchemaUpdateEvent =>
// eslint-disable-next-line no-restricted-syntax
newSchemaUpdateEvents.includes(event as NewSchemaUpdateEvent);

const alteration: AlterationScript = {
up: async (pool) => {
const { rows: hooks } = await pool.query<{ id: string; events: string[] }>(sql`
select id, events
from hooks
`);

const hooksToBeUpdate = hooks.filter(({ events }) => {
return oldSchemaUpdateEvents.some((oldEvent) => events.includes(oldEvent));
});

await Promise.all(
hooksToBeUpdate.map(async ({ id, events }) => {
const updateEvents = events.reduce<string[]>((accumulator, event) => {
if (isOldSchemaUpdateEvent(event)) {
return [...accumulator, updateMap[event]];
}
return [...accumulator, event];
}, []);

await pool.query(sql`
update hooks
set events = ${JSON.stringify(updateEvents)}
where id = ${id};
`);
})
);
},
down: async (pool) => {
const { rows: hooks } = await pool.query<{ id: string; events: string[] }>(sql`
select id, events
from hooks
`);

const hooksToBeUpdate = hooks.filter(({ events }) => {
return newSchemaUpdateEvents.some((newEvent) => events.includes(newEvent));
});

await Promise.all(
hooksToBeUpdate.map(async ({ id, events }) => {
const updateEvents = events.reduce<string[]>((accumulator, event) => {
if (isNewSchemaUpdateEvent(event)) {
return [...accumulator, reverseMap[event]];
}
return [...accumulator, event];
}, []);

await pool.query(sql`
update hooks
set events = ${JSON.stringify(updateEvents)}
where id = ${id};
`);
})
);
},
};

export default alteration;
Loading

0 comments on commit 5e7bee1

Please sign in to comment.