mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat: post affiliate data to cloud (#4321)
* feat: post affiliate data to cloud Read data from cookie and post to cloud when needed. * chore: add alteration script * refactor: fix alteration
This commit is contained in:
parent
1c18111344
commit
2901f43e64
5 changed files with 120 additions and 8 deletions
|
@ -28,6 +28,7 @@
|
|||
"@aws-sdk/client-s3": "^3.315.0",
|
||||
"@azure/storage-blob": "^12.13.0",
|
||||
"@koa/cors": "^4.0.0",
|
||||
"@logto/affiliate": "^0.1.0",
|
||||
"@logto/app-insights": "workspace:^1.3.1",
|
||||
"@logto/cli": "workspace:^1.7.0",
|
||||
"@logto/connector-kit": "workspace:^1.1.1",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { defaults, parseAffiliateData } from '@logto/affiliate';
|
||||
import { Component, CoreEvent, getEventName } from '@logto/app-insights/custom-event';
|
||||
import { appInsights } from '@logto/app-insights/node';
|
||||
import type { User, Profile, CreateUser } from '@logto/schemas';
|
||||
|
@ -11,14 +12,17 @@ import {
|
|||
adminConsoleApplicationId,
|
||||
} from '@logto/schemas';
|
||||
import { type OmitAutoSetFields } from '@logto/shared';
|
||||
import { conditional, conditionalArray } from '@silverhand/essentials';
|
||||
import { conditional, conditionalArray, trySafe } from '@silverhand/essentials';
|
||||
import { type IRouterContext } from 'koa-router';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import { assignInteractionResults } from '#src/libraries/session.js';
|
||||
import { encryptUserPassword } from '#src/libraries/user.js';
|
||||
import type { LogEntry, WithLogContext } from '#src/middleware/koa-audit-log.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import { consoleLog } from '#src/utils/console.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
import type { WithInteractionDetailsContext } from '../middleware/koa-interaction-details.js';
|
||||
|
@ -164,10 +168,45 @@ const parseUserProfile = async (
|
|||
};
|
||||
};
|
||||
|
||||
const getInitialUserRoles = (
|
||||
isInAdminTenant: boolean,
|
||||
isCreatingFirstAdminUser: boolean,
|
||||
isCloud: boolean
|
||||
) =>
|
||||
conditionalArray<string>(
|
||||
isInAdminTenant && AdminTenantRole.User,
|
||||
isCreatingFirstAdminUser && getManagementApiAdminName(defaultTenantId),
|
||||
isCreatingFirstAdminUser && isCloud && getManagementApiAdminName(adminTenantId)
|
||||
);
|
||||
|
||||
/** Post affiliate data to the cloud service. */
|
||||
const postAffiliateLogs = async (
|
||||
ctx: IRouterContext,
|
||||
cloudConnection: CloudConnectionLibrary,
|
||||
userId: string,
|
||||
tenantId: string
|
||||
) => {
|
||||
if (!EnvSet.values.isCloud || tenantId !== adminTenantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const affiliateData = trySafe(() =>
|
||||
parseAffiliateData(JSON.parse(decodeURIComponent(ctx.cookies.get(defaults.cookieName) ?? '')))
|
||||
);
|
||||
|
||||
if (affiliateData) {
|
||||
const client = await cloudConnection.getClient();
|
||||
await client.post('/api/affiliate-logs', {
|
||||
body: { userId, ...affiliateData },
|
||||
});
|
||||
consoleLog.info('Affiliate logs posted', userId);
|
||||
}
|
||||
};
|
||||
|
||||
export default async function submitInteraction(
|
||||
interaction: VerifiedInteractionResult,
|
||||
ctx: WithLogContext & WithInteractionDetailsContext & WithInteractionHooksContext,
|
||||
{ provider, libraries, connectors, queries }: TenantContext,
|
||||
{ provider, libraries, connectors, queries, cloudConnection, id: tenantId }: TenantContext,
|
||||
log?: LogEntry
|
||||
) {
|
||||
const { hasActiveUsers, findUserById, updateUserById } = queries.users;
|
||||
|
@ -196,11 +235,7 @@ export default async function submitInteraction(
|
|||
id,
|
||||
...userProfile,
|
||||
},
|
||||
conditionalArray<string>(
|
||||
isInAdminTenant && AdminTenantRole.User,
|
||||
isCreatingFirstAdminUser && getManagementApiAdminName(defaultTenantId),
|
||||
isCreatingFirstAdminUser && isCloud && getManagementApiAdminName(adminTenantId)
|
||||
)
|
||||
getInitialUserRoles(isInAdminTenant, isCreatingFirstAdminUser, isCloud)
|
||||
);
|
||||
|
||||
// In OSS, we need to limit sign-in experience to "sign-in only" once
|
||||
|
@ -216,6 +251,10 @@ export default async function submitInteraction(
|
|||
|
||||
log?.append({ userId: id });
|
||||
appInsights.client?.trackEvent({ name: getEventName(Component.Core, CoreEvent.Register) });
|
||||
void trySafe(postAffiliateLogs(ctx, cloudConnection, id, tenantId), (error) => {
|
||||
consoleLog.warn('Failed to post affiliate logs', error);
|
||||
void appInsights.trackException(error);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { generateStandardId } from '@logto/shared/universal';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const adminTenantId = 'admin';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
// Get `resourceId` of the admin tenant's resource whose indicator is `https://cloud.logto.io/api`.
|
||||
const { id: resourceId } = await pool.one<{ id: string }>(sql`
|
||||
select id from resources
|
||||
where tenant_id = ${adminTenantId}
|
||||
and indicator = 'https://cloud.logto.io/api'
|
||||
`);
|
||||
|
||||
const { id: roleId } = await pool.one<{ id: string }>(sql`
|
||||
select id from roles
|
||||
where tenant_id = ${adminTenantId}
|
||||
and name = 'admin:admin'
|
||||
`);
|
||||
|
||||
const createAffiliateId = generateStandardId();
|
||||
const manageAffiliateId = generateStandardId();
|
||||
|
||||
await pool.query(sql`
|
||||
insert into scopes (tenant_id, id, name, description, resource_id)
|
||||
values (
|
||||
${adminTenantId},
|
||||
${createAffiliateId},
|
||||
'create:affiliate',
|
||||
'Allow creating new affiliates and logs.',
|
||||
${resourceId}
|
||||
), (
|
||||
${adminTenantId},
|
||||
${manageAffiliateId},
|
||||
'manage:affiliate',
|
||||
'Allow managing affiliates, including create, update, and delete.',
|
||||
${resourceId}
|
||||
);
|
||||
`);
|
||||
|
||||
await pool.query(sql`
|
||||
insert into roles_scopes (tenant_id, id, role_id, scope_id) values
|
||||
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${createAffiliateId}),
|
||||
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${manageAffiliateId});
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
delete from scopes
|
||||
where tenant_id = ${adminTenantId} and name = any(array['create:affiliate', 'manage:affiliate']);
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -66,6 +66,11 @@ export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]>
|
|||
CloudScope.SendSms,
|
||||
'Allow sending SMS. This scope is only available to M2M application.'
|
||||
),
|
||||
buildScope(CloudScope.CreateAffiliate, 'Allow creating new affiliates and logs.'),
|
||||
buildScope(
|
||||
CloudScope.ManageAffiliate,
|
||||
'Allow managing affiliates, including create, update, and delete.'
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
|
@ -3124,6 +3124,9 @@ importers:
|
|||
'@koa/cors':
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
'@logto/affiliate':
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
'@logto/app-insights':
|
||||
specifier: workspace:^1.3.1
|
||||
version: link:../app-insights
|
||||
|
@ -7290,6 +7293,14 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/@logto/affiliate@0.1.0:
|
||||
resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==}
|
||||
engines: {node: ^18.12.0}
|
||||
dependencies:
|
||||
'@silverhand/essentials': 2.8.2
|
||||
tiny-cookie: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@logto/browser@2.1.0:
|
||||
resolution: {integrity: sha512-4XsXlCC0uZHcfazV09/4YKo4koqvSzQlkPUAToTp/WHpb6h2XDOJh5/hi55LXL4zp0PCcgpErKRxFCtgXCc6WQ==}
|
||||
dependencies:
|
||||
|
@ -19705,7 +19716,6 @@ packages:
|
|||
|
||||
/tiny-cookie@2.4.1:
|
||||
resolution: {integrity: sha512-h8ueaMyvUd/9ZfRqCfa1t+0tXqfVFhdK8WpLHz8VXMqsiaj3Sqg64AOCH/xevLQGZk0ZV+/75ouITdkvp3taVA==}
|
||||
dev: true
|
||||
|
||||
/tiny-glob@0.2.9:
|
||||
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
|
||||
|
|
Loading…
Reference in a new issue