mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(core,schemas,test): rename DataHook data update event name (#5876)
rename the DataHook Schema data update event name
This commit is contained in:
parent
c558affac5
commit
5e7bee1c8c
7 changed files with 177 additions and 41 deletions
|
@ -372,7 +372,7 @@ describe('submit action', () => {
|
|||
login: { accountId: 'foo' },
|
||||
});
|
||||
expect(ctx.assignDataHookContext).toBeCalledWith({
|
||||
event: 'User.Updated',
|
||||
event: 'User.Data.Updated',
|
||||
user: updateProfile,
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -221,7 +221,7 @@ describe('interaction api trigger hooks', () => {
|
|||
toBeUndefined: true,
|
||||
});
|
||||
|
||||
await assertHookLogResult(dataHook, 'User.Updated', {
|
||||
await assertHookLogResult(dataHook, 'User.Data.Updated', {
|
||||
toBeUndefined: true,
|
||||
});
|
||||
});
|
||||
|
@ -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 }),
|
||||
|
@ -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 }),
|
||||
|
|
|
@ -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' },
|
||||
|
@ -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' },
|
||||
|
@ -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() },
|
||||
|
@ -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' },
|
||||
|
@ -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' },
|
||||
|
@ -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() },
|
||||
|
|
|
@ -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]');
|
||||
|
|
|
@ -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;
|
|
@ -27,6 +27,9 @@ export enum DataHookSchema {
|
|||
enum DataHookBasicMutationType {
|
||||
Created = 'Created',
|
||||
Deleted = 'Deleted',
|
||||
}
|
||||
|
||||
enum DataHookDetailMutationType {
|
||||
Updated = 'Updated',
|
||||
}
|
||||
|
||||
|
@ -34,13 +37,14 @@ type BasicDataHookEvent = `${DataHookSchema}.${DataHookBasicMutationType}`;
|
|||
|
||||
// Custom DataHook mutable schemas
|
||||
type CustomDataHookMutableSchema =
|
||||
| `${DataHookSchema}.Data`
|
||||
| `${DataHookSchema.User}.SuspensionStatus`
|
||||
| `${DataHookSchema.Role}.Scopes`
|
||||
| `${DataHookSchema.Organization}.Membership`
|
||||
| `${DataHookSchema.OrganizationRole}.Scopes`;
|
||||
|
||||
type DataHookPropertyUpdateEvent =
|
||||
`${CustomDataHookMutableSchema}.${DataHookBasicMutationType.Updated}`;
|
||||
`${CustomDataHookMutableSchema}.${DataHookDetailMutationType.Updated}`;
|
||||
|
||||
export type DataHookEvent = BasicDataHookEvent | DataHookPropertyUpdateEvent;
|
||||
|
||||
|
@ -51,26 +55,26 @@ export const hookEvents = Object.freeze([
|
|||
InteractionHookEvent.PostResetPassword,
|
||||
'User.Created',
|
||||
'User.Deleted',
|
||||
'User.Updated',
|
||||
'User.Data.Updated',
|
||||
'User.SuspensionStatus.Updated',
|
||||
'Role.Created',
|
||||
'Role.Deleted',
|
||||
'Role.Updated',
|
||||
'Role.Data.Updated',
|
||||
'Role.Scopes.Updated',
|
||||
'Scope.Created',
|
||||
'Scope.Deleted',
|
||||
'Scope.Updated',
|
||||
'Scope.Data.Updated',
|
||||
'Organization.Created',
|
||||
'Organization.Deleted',
|
||||
'Organization.Updated',
|
||||
'Organization.Data.Updated',
|
||||
'Organization.Membership.Updated',
|
||||
'OrganizationRole.Created',
|
||||
'OrganizationRole.Deleted',
|
||||
'OrganizationRole.Updated',
|
||||
'OrganizationRole.Data.Updated',
|
||||
'OrganizationRole.Scopes.Updated',
|
||||
'OrganizationScope.Created',
|
||||
'OrganizationScope.Deleted',
|
||||
'OrganizationScope.Updated',
|
||||
'OrganizationScope.Data.Updated',
|
||||
] as const satisfies Array<InteractionHookEvent | DataHookEvent>);
|
||||
|
||||
/** The type of hook event values that can be registered. */
|
||||
|
@ -114,31 +118,31 @@ type ApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|||
export const managementApiHooksRegistration = Object.freeze({
|
||||
'POST /users': 'User.Created',
|
||||
'DELETE /users/:userId': 'User.Deleted',
|
||||
'PATCH /users/:userId': 'User.Updated',
|
||||
'PATCH /users/:userId/custom-data': 'User.Updated',
|
||||
'PATCH /users/:userId/profile': 'User.Updated',
|
||||
'PATCH /users/:userId/password': 'User.Updated',
|
||||
'PATCH /users/:userId': 'User.Data.Updated',
|
||||
'PATCH /users/:userId/custom-data': 'User.Data.Updated',
|
||||
'PATCH /users/:userId/profile': 'User.Data.Updated',
|
||||
'PATCH /users/:userId/password': 'User.Data.Updated',
|
||||
'PATCH /users/:userId/is-suspended': 'User.SuspensionStatus.Updated',
|
||||
'POST /roles': 'Role.Created',
|
||||
'DELETE /roles/:id': 'Role.Deleted',
|
||||
'PATCH /roles/:id': 'Role.Updated',
|
||||
'PATCH /roles/:id': 'Role.Data.Updated',
|
||||
'POST /roles/:id/scopes': 'Role.Scopes.Updated',
|
||||
'DELETE /roles/:id/scopes/:scopeId': 'Role.Scopes.Updated',
|
||||
'POST /resources/:resourceId/scopes': 'Scope.Created',
|
||||
'DELETE /resources/:resourceId/scopes/:scopeId': 'Scope.Deleted',
|
||||
'PATCH /resources/:resourceId/scopes/:scopeId': 'Scope.Updated',
|
||||
'PATCH /resources/:resourceId/scopes/:scopeId': 'Scope.Data.Updated',
|
||||
'POST /organizations': 'Organization.Created',
|
||||
'DELETE /organizations/:id': 'Organization.Deleted',
|
||||
'PATCH /organizations/:id': 'Organization.Updated',
|
||||
'PATCH /organizations/:id': 'Organization.Data.Updated',
|
||||
'PUT /organizations/:id/users': 'Organization.Membership.Updated',
|
||||
'POST /organizations/:id/users': 'Organization.Membership.Updated',
|
||||
'DELETE /organizations/:id/users/:userId': 'Organization.Membership.Updated',
|
||||
'POST /organization-roles': 'OrganizationRole.Created',
|
||||
'DELETE /organization-roles/:id': 'OrganizationRole.Deleted',
|
||||
'PATCH /organization-roles/:id': 'OrganizationRole.Updated',
|
||||
'PATCH /organization-roles/:id': 'OrganizationRole.Data.Updated',
|
||||
'POST /organization-scopes': 'OrganizationScope.Created',
|
||||
'DELETE /organization-scopes/:id': 'OrganizationScope.Deleted',
|
||||
'PATCH /organization-scopes/:id': 'OrganizationScope.Updated',
|
||||
'PATCH /organization-scopes/:id': 'OrganizationScope.Data.Updated',
|
||||
'PUT /organization-roles/:id/scopes': 'OrganizationRole.Scopes.Updated',
|
||||
'POST /organization-roles/:id/scopes': 'OrganizationRole.Scopes.Updated',
|
||||
'DELETE /organization-roles/:id/scopes/:organizationScopeId': 'OrganizationRole.Scopes.Updated',
|
||||
|
|
Loading…
Add table
Reference in a new issue