diff --git a/package-lock.json b/package-lock.json index 8eef1fbcf..cc129ba52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@aws-sdk/lib-dynamodb": "^3.751.0", "@middy/core": "^6.0.0", "@middy/http-cors": "^6.0.0", - "@notifycal/shared": "4.9.0", + "@notifycal/shared": "5.0.0", "@vonage/server-sdk": "^3.20.0", "axios": "^1.8.4", "env-var": "^7.5.0", @@ -2403,9 +2403,9 @@ } }, "node_modules/@notifycal/shared": { - "version": "4.9.0", - "resolved": "https://npm.pkg.github.com/download/@notifycal/shared/4.9.0/7a66fd58e996e8a5fda97bd85434a575d4ae0033", - "integrity": "sha512-WOH++q+6czbj8yjzetNWVKoGWaSqVf82VzZ5J3x5DQTgafo6D+KamPpyZKR8HU4t1ZUjhQvuIHYpdFlwzczi9w==", + "version": "5.0.0", + "resolved": "https://npm.pkg.github.com/download/@notifycal/shared/5.0.0/02c6896e8c4df9bbc0d8eff6e294a3f8015523fe", + "integrity": "sha512-nGXX81H6gPAEg9Yo48l2uTZLY5KFnp4JS0LM4ooPjEIPKZXnTTlGeKYuD2mrk4AtT0rtk9r6p92V4dJo4mKBRQ==", "license": "ISC", "dependencies": { "radashi": "^12.6.0" diff --git a/package.json b/package.json index 687a0ed54..89623c0a0 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@aws-sdk/lib-dynamodb": "^3.751.0", "@middy/core": "^6.0.0", "@middy/http-cors": "^6.0.0", - "@notifycal/shared": "4.9.0", + "@notifycal/shared": "5.0.0", "@vonage/server-sdk": "^3.20.0", "axios": "^1.8.4", "env-var": "^7.5.0", diff --git a/src/lambdas/api/post-demo-reminder/index.ts b/src/lambdas/api/post-demo-reminder/index.ts index 917698e56..d5d97d727 100644 --- a/src/lambdas/api/post-demo-reminder/index.ts +++ b/src/lambdas/api/post-demo-reminder/index.ts @@ -10,9 +10,9 @@ import type { CorrelationId, DateTime, EventId, - Identity, IdpName, - TemplateId + TemplateId, + UserIdentity } from '@notifycal/shared/types'; import { errorHandler, successHandler } from '@services/common/api-response-handlers'; import { SnsService } from '@services/sns'; @@ -48,7 +48,7 @@ function buildEvent( requestBody: Event['body'], userReminderConfig: LiveUserStoreRecord['Config'], templateId: TemplateId, - identity: Identity + userIdentity: UserIdentity ): DemoReminderToBeSentEvent { const eventId = v4(); const message = interpolate( @@ -63,9 +63,9 @@ function buildEvent( correlationId: eventId as CorrelationId, eventType: 'DemoReminderToBeSent', happenedAt: new Date().toISOString() as DateTime, - userId: identity.userId, - idp: identity.idp, - idpId: identity.idpId, + userId: userIdentity.userId, + idp: userIdentity.idp, + idpId: userIdentity.idpId, data: { senderDetails: senderToCanonicalForm( fromStoreRecord(userReminderConfig.Business.SenderContact) diff --git a/src/lambdas/api/post-login/index.test.ts b/src/lambdas/api/post-login/index.test.ts index 739709ad0..8fc68ba23 100644 --- a/src/lambdas/api/post-login/index.test.ts +++ b/src/lambdas/api/post-login/index.test.ts @@ -2,11 +2,11 @@ import type { Algorithm } from '@model/Config'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import type { Email, - Identity, IdpId, IdpName, Jwt, UnixTimestamp, + UserIdentity, Uuid } from '@notifycal/shared/types'; import type { AwsArn, PrivateKey } from '@own-types/model'; @@ -41,7 +41,7 @@ import { handler, type Event } from './index'; describe('POST Login', () => { const userEmail = 'test@notifycal.com' as Email; const validUserId = validJwts.accessToken.decoded.payload.userId; - const validIdentity: Identity<'google.com'> = { + const validIdentity: UserIdentity<'google.com'> = { userId: validJwts.accessToken.decoded.payload.userId, email: userEmail, idp: 'google.com', @@ -51,7 +51,7 @@ describe('POST Login', () => { refreshToken: 'some_google_refressssh_token' }; const validVerifyGoogleIdentityFn = (): Promise< - [Identity<'google.com'>, AuthorizationForIdp<'google.com'>] + [UserIdentity<'google.com'>, AuthorizationForIdp<'google.com'>] > => Promise.resolve([validIdentity, validAuthorization]); const validQueryParams = { idp: 'google.com' @@ -252,7 +252,7 @@ describe('POST Login', () => { // eslint-disable-next-line @typescript-eslint/require-await async function testit( event: APIGatewayProxyEvent, - verifyGoogleIdentityFn: () => Promise<[Identity, AuthorizationForIdp]>, + verifyGoogleIdentityFn: () => Promise<[UserIdentity, AuthorizationForIdp]>, signInOrUpUserFn: () => Promise, env: LoginConfig = defaultEnv ): Promise { diff --git a/src/lambdas/api/post-login/index.ts b/src/lambdas/api/post-login/index.ts index 39ae21e54..fd5e1f6a5 100644 --- a/src/lambdas/api/post-login/index.ts +++ b/src/lambdas/api/post-login/index.ts @@ -4,7 +4,7 @@ import { logger } from '@common/powertools'; import type { IdpConfigs } from '@model/Config'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import { apiEventSchema } from '@model/lambda-events/ApiGatewayEvents'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { _successHandler, signInOrUp } from '@services/auth'; import { errorHandler } from '@services/common/api-response-handlers'; @@ -30,7 +30,7 @@ function verifyIdentity( event: Event, idpQueryParameter: string | undefined, config: IdpConfigs -): Promise<[Identity, AuthorizationForIdp]> { +): Promise<[UserIdentity, AuthorizationForIdp]> { const origin = event.headers?.origin || event.headers?.Origin || event.headers?.ORIGIN; if (isValidIdpName(idpQueryParameter) && idpQueryParameter === 'google.com' && origin) { return GoogleOAuth.withConfig(config['google.com'], origin as Url, logger).verifyIdentity( @@ -51,13 +51,13 @@ function lambdaHandler( const idpQueryPath = event.queryStringParameters?.['idp']; return verifyIdentity(event, idpQueryPath, config.idpConfigs) - .then(([identity, idpAuthorization]) => { + .then(([userIdentity, idpAuthorization]) => { logger.appendKeys({ - userId: identity.userId, - idp: identity.idp, - idpId: identity.idpId + userId: userIdentity.userId, + idp: userIdentity.idp, + idpId: userIdentity.idpId }); - return signInOrUp(identity, idpAuthorization, config, logger) + return signInOrUp(userIdentity, idpAuthorization, config, logger) .then(_successHandler) .catch(errorHandler(500)); }) diff --git a/src/lambdas/api/post-payment-session/index.ts b/src/lambdas/api/post-payment-session/index.ts index fd3ec999a..6d0783b41 100644 --- a/src/lambdas/api/post-payment-session/index.ts +++ b/src/lambdas/api/post-payment-session/index.ts @@ -3,7 +3,7 @@ import { corsErrorResponse } from '@common/cors-middleware'; import { protectedEndpointMiddleware } from '@common/lambda-middleware'; import { logger, metrics } from '@common/powertools'; import type { Tier, Topup } from '@model/PaymentPlans'; -import type { Identity, IdpName, StripeCustomerId } from '@notifycal/shared/types'; +import type { IdpName, StripeCustomerId, UserIdentity } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { errorHandler, @@ -19,15 +19,15 @@ import { readPostPaymentCheckoutSessionConfig } from './config'; import { type Event, eventSchema } from './schemas'; function createCustomerOrRetrieve( - identity: Identity, + userIdentity: UserIdentity, userBaseStore: UserBaseStore, stripeService: StripeService ): Promise { return userBaseStore - .getStripeCustomerId(identity.userId) + .getStripeCustomerId(userIdentity.userId) .catch((error) => { logger.error('Failed to get stripe customer ID from database', { - userId: identity.userId, + userId: userIdentity.userId, error }); throw error; @@ -37,21 +37,21 @@ function createCustomerOrRetrieve( return Promise.resolve(stripeCustomerIdOrNot); } else { return stripeService - .createCustomer(identity) + .createCustomer(userIdentity) .catch((error) => { logger.error('Failed to create stripe customer', { - userId: identity.userId, - email: identity.email, + userId: userIdentity.userId, + email: userIdentity.email, error }); throw error; }) .then((stripeCustomerId) => userBaseStore - .setStripeCustomerId(identity.userId, stripeCustomerId) + .setStripeCustomerId(userIdentity.userId, stripeCustomerId) .catch((error) => { logger.error('Failed to save stripe customer ID to database', { - userId: identity.userId, + userId: userIdentity.userId, stripeCustomerId, error }); @@ -67,7 +67,7 @@ function checkEligibility( stripeCustomerId: StripeCustomerId, selectedProduct: Tier | Topup, stripeService: StripeService, - identity: Identity + userIdentity: UserIdentity ): Promise<{ eligible: boolean; stripeCustomerId: StripeCustomerId }> { if (selectedProduct.type === 'topup') { return Promise.resolve({ eligible: true, stripeCustomerId }); @@ -76,7 +76,7 @@ function checkEligibility( return stripeService.countSubscriptions(stripeCustomerId).then((activeSubscriptionCount) => { if (activeSubscriptionCount >= 1) { logger.info('Customer already has an active subscription', { - userId: identity.userId, + userId: userIdentity.userId, stripeCustomerId, activeSubscriptionCount }); @@ -92,7 +92,7 @@ async function lambdaHandler( _ctx: Context ): Promise { const { userId, idp, idpId, email } = event.requestContext.authorizer.payload; - const identity = { userId, idp, idpId, email }; + const userIdentity = { userId, idp, idpId, email }; const { stripeAuthConfig, stripeCheckoutConfig, paymentPlans, userBaseStoreConfig } = event.lambdaConfig; const apiKey = stripeAuthConfig.apiKey; @@ -121,9 +121,9 @@ async function lambdaHandler( const userBaseStore = UserBaseStore.withConfig(userBaseStoreConfig, logger); const stripeService = await StripeService.withConfig(apiKey); - return createCustomerOrRetrieve(identity, userBaseStore, stripeService) + return createCustomerOrRetrieve(userIdentity, userBaseStore, stripeService) .then((stripeCustomerId) => - checkEligibility(stripeCustomerId, selectedProduct, stripeService, identity) + checkEligibility(stripeCustomerId, selectedProduct, stripeService, userIdentity) ) .then((eligibilityResult) => { if (!eligibilityResult.eligible) { @@ -139,7 +139,7 @@ async function lambdaHandler( return stripeService .createCheckoutSession( eligibilityResult.stripeCustomerId, - identity, + userIdentity, selectedProduct, language, successRedirectUrl, @@ -159,7 +159,7 @@ async function lambdaHandler( }, (error) => { logger.error('Failed to create stripe checkout session', { - userId: identity.userId, + userId: userIdentity.userId, stripeCustomerId: eligibilityResult.stripeCustomerId, product: selectedProduct.id, error diff --git a/src/lambdas/api/post-refresh/index.ts b/src/lambdas/api/post-refresh/index.ts index f49bb49fd..32594fac9 100644 --- a/src/lambdas/api/post-refresh/index.ts +++ b/src/lambdas/api/post-refresh/index.ts @@ -3,7 +3,7 @@ import { unprotectedCrossDomainEndpointMiddleware } from '@common/lambda-middlew import { logger } from '@common/powertools'; import { refreshTokenSchema } from '@model/Jwt'; import { apiEventSchema } from '@model/lambda-events/ApiGatewayEvents'; -import { extractIdentity } from '@model/UserIdentity'; +import { extractUserIdentity } from '@model/UserIdentity'; import type { Jwt } from '@notifycal/shared/types'; import { _successHandler, buildJwtsAndStoreRefreshJwt } from '@services/auth'; import { errorHandler } from '@services/common/api-response-handlers'; @@ -53,7 +53,7 @@ function lambdaHandler( idpId: user.IdpId }); return buildJwtsAndStoreRefreshJwt( - extractIdentity(user), + extractUserIdentity(user), config.encodeAccessJwtConfig, config.encodeRefreshJwtConfig, refreshTokenStore diff --git a/src/lambdas/dynamodb-streams/alert-for-events/record-processor.ts b/src/lambdas/dynamodb-streams/alert-for-events/record-processor.ts index 78d163f8e..ea901894e 100644 --- a/src/lambdas/dynamodb-streams/alert-for-events/record-processor.ts +++ b/src/lambdas/dynamodb-streams/alert-for-events/record-processor.ts @@ -53,7 +53,7 @@ function processAlertEvent( logger: Logger ): Promise { logger.info(`Processing alert event`, { eventType: event.EventType }); - const identity: EventSourceIdentity = extractIdentity(event); + const userIdentity: EventSourceIdentity = extractIdentity(event); const options: EventCreationOptions = { correlationId: event.CorrelationId }; @@ -64,7 +64,7 @@ function processAlertEvent( templateConfig, event.EventType, { eventType: event.EventType }, - identity, + userIdentity, options ); return snsService.publish(alertEvent).then(); diff --git a/src/lambdas/dynamodb-streams/alert-for-missing-phone-number/record-processor.ts b/src/lambdas/dynamodb-streams/alert-for-missing-phone-number/record-processor.ts index 49aedda07..34894f032 100644 --- a/src/lambdas/dynamodb-streams/alert-for-missing-phone-number/record-processor.ts +++ b/src/lambdas/dynamodb-streams/alert-for-missing-phone-number/record-processor.ts @@ -91,7 +91,7 @@ function createEmailEvent( errorRate, notificationsSentCountBeforeUpdate: updateCounterResult.NotificationSentCount }; - const identity: EventSourceIdentity = extractIdentity(event); + const userIdentity: EventSourceIdentity = extractIdentity(event); const options: EventCreationOptions = { correlationId: event.CorrelationId }; @@ -103,7 +103,7 @@ function createEmailEvent( templateConfig, subEventType, metadata, - identity, + userIdentity, options ); } diff --git a/src/lambdas/sqs/fetch-user-calendars/index.ts b/src/lambdas/sqs/fetch-user-calendars/index.ts index 185e159fc..e22c83a48 100644 --- a/src/lambdas/sqs/fetch-user-calendars/index.ts +++ b/src/lambdas/sqs/fetch-user-calendars/index.ts @@ -13,7 +13,7 @@ import { fromStoreRecord as fromContactStoreRecord } from '@model/store/ContactD import type { LiveUserStoreRecord } from '@model/store/LiveUserStoreRecord'; import { fromStoreRecord } from '@model/store/ReminderConfigStoreRecord'; import type { UserIdpAuthorizationStoreRecord } from '@model/store/UserIdpAuthorizationStoreRecord'; -import { extractIdentity } from '@model/UserIdentity'; +import { extractUserIdentity } from '@model/UserIdentity'; import type { CorrelationId, DateTime, EventId } from '@notifycal/shared/types'; import { setupLoggerCorrelationIdEventBridge } from '@services/common/logger'; import { SnsService } from '@services/sns'; @@ -131,7 +131,7 @@ async function lambdaHandler(event: Event, _context: Context): Promise { if (user.Config.Calendars && user.Config.Calendars.length > 0) { return Promise.resolve(toEvents(user, run)); } else { - const errorEvent = noUserCalendarFound(record, run, extractIdentity(user)); + const errorEvent = noUserCalendarFound(record, run, extractUserIdentity(user)); return snsService.safePublish(errorEvent).then(() => []); } }) diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/checkout.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/checkout.ts index 03523d404..4397654d3 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/checkout.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/checkout.ts @@ -1,5 +1,5 @@ import type { Logger } from '@aws-lambda-powertools/logger'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { Stripe } from 'stripe'; import type { StripeEventType } from '../stripe-schemas'; import { BaseHandler } from './base-handler'; @@ -18,13 +18,13 @@ export class CheckoutSessionCompletedHandler public handle( event: Stripe.CheckoutSessionCompletedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const session = event.data.object; this.logger.info('Handling checkout session completed', { checkoutSessionId: session.id, customerId: session.customer, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/common.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/common.ts index e8b66c3a3..9a02025eb 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/common.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/common.ts @@ -1,4 +1,4 @@ -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type Stripe from 'stripe'; import type { StripeEventType } from '../stripe-schemas'; @@ -6,5 +6,5 @@ export type EventHandlerBuilder = ( type: StripeEventType ) => EventHandler; export interface EventHandler { - handle(event: T, identity: Identity): Promise; + handle(event: T, userIdentity: UserIdentity): Promise; } diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/customer.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/customer.ts index 02dd4b528..b40afd73a 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/customer.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/customer.ts @@ -1,5 +1,5 @@ import type { Logger } from '@aws-lambda-powertools/logger'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type Stripe from 'stripe'; import type { StripeEventType } from '../stripe-schemas'; import { BaseHandler } from './base-handler'; @@ -16,12 +16,15 @@ export class CustomerCreatedHandler super(stripeEventType); } - public handle(event: Stripe.CustomerCreatedEvent, identity: Identity): Promise { + public handle( + event: Stripe.CustomerCreatedEvent, + userIdentity: UserIdentity + ): Promise { const customer = event.data.object; this.logger.info('Handling customer created', { customerId: customer.id, email: customer.email, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -38,13 +41,16 @@ export class CustomerUpdatedHandler super(stripeEventType); } - public handle(event: Stripe.CustomerUpdatedEvent, identity: Identity): Promise { + public handle( + event: Stripe.CustomerUpdatedEvent, + userIdentity: UserIdentity + ): Promise { const customer = event.data.object; const previousAttributes = event.data.previous_attributes; this.logger.info('Handling customer updated', { customerId: customer.id, updatedFields: Object.keys(previousAttributes || {}), - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -61,11 +67,14 @@ export class CustomerDeletedHandler super(stripeEventType); } - public handle(event: Stripe.CustomerDeletedEvent, identity: Identity): Promise { + public handle( + event: Stripe.CustomerDeletedEvent, + userIdentity: UserIdentity + ): Promise { const customer = event.data.object; this.logger.info('Handling customer deleted', { customerId: customer.id, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.test.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.test.ts index 0fbb77b9f..d27f8cb42 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.test.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.test.ts @@ -4,12 +4,12 @@ import type { CreditAdditionResult } from '@model/Credits'; import type { TierMap, TopupMap } from '@model/PaymentPlans'; import type { Email, - Identity, IdpId, IdpName, TierId, TopupId, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import type { SubscriptionService } from '@services/subscription'; import type { TopupService } from '@services/topup'; @@ -19,7 +19,7 @@ import { describe, expect, it, vi, type Mock } from 'vitest'; import { InvoicePaymentSucceededHandler } from './invoice'; describe(InvoicePaymentSucceededHandler, () => { - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: 'user-123' as UserId, email: 'user@example.com' as Email, idp: 'google.com', @@ -853,18 +853,21 @@ describe(InvoicePaymentSucceededHandler, () => { function testIt( event: Stripe.InvoicePaymentSucceededEvent, - identity: Identity, - createFn: (identity: Identity, tierId: TierId) => Promise, - renewFn: (identity: Identity, tierId: TierId) => Promise, + userIdentity: UserIdentity, + createFn: ( + userIdentity: UserIdentity, + tierId: TierId + ) => Promise, + renewFn: (userIdentity: UserIdentity, tierId: TierId) => Promise, upgradeFn: ( - identity: Identity, + userIdentity: UserIdentity, previousTier: TierId, currentTier: TierId, remainingPercentage: number ) => Promise, - downgradeFn: (identity: Identity) => Promise, + downgradeFn: (userIdentity: UserIdentity) => Promise, addTopupFn: ( - identity: Identity, + userIdentity: UserIdentity, topupId: TopupId, quantity: number ) => Promise, @@ -897,6 +900,6 @@ describe(InvoicePaymentSucceededHandler, () => { loggerMock ); - return handler.handle(event, identity); + return handler.handle(event, userIdentity); } }); diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.ts index 04c1f9cd5..eb77ea95a 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/invoice.ts @@ -2,7 +2,7 @@ import type { Logger } from '@aws-lambda-powertools/logger'; import type { CreditAdditionResult } from '@model/Credits'; import type { TierMap, TopupMap } from '@model/PaymentPlans'; -import type { Identity, IdpName, TierId, TopupId } from '@notifycal/shared/types'; +import type { IdpName, TierId, TopupId, UserIdentity } from '@notifycal/shared/types'; import type { SubscriptionService } from '@services/subscription'; import type { TopupService } from '@services/topup'; import type Stripe from 'stripe'; @@ -22,13 +22,16 @@ export class InvoiceCreatedHandler super(stripeEventType); } - public handle(event: Stripe.InvoiceCreatedEvent, identity: Identity): Promise { + public handle( + event: Stripe.InvoiceCreatedEvent, + userIdentity: UserIdentity + ): Promise { const invoice = event.data.object; this.logger.info('Handling invoice created', { invoiceId: invoice.id, customerId: invoice.customer, amount: invoice.amount_due, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -51,7 +54,7 @@ export class InvoicePaymentSucceededHandler public handle( event: Stripe.InvoicePaymentSucceededEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const invoice = event.data.object; this.logger.info('Handling invoice payment succeeded', { @@ -59,15 +62,19 @@ export class InvoicePaymentSucceededHandler customerId: invoice.customer, amount: invoice.amount_paid, billingReason: invoice.billing_reason, - userId: identity.userId + userId: userIdentity.userId }); return match(invoice) - .with({ billing_reason: 'subscription_create' }, () => this.createHandler(invoice, identity)) - .with({ billing_reason: 'subscription_cycle' }, () => this.renewHandler(invoice, identity)) + .with({ billing_reason: 'subscription_create' }, () => + this.createHandler(invoice, userIdentity) + ) + .with({ billing_reason: 'subscription_cycle' }, () => + this.renewHandler(invoice, userIdentity) + ) .with({ billing_reason: 'subscription_update' }, (invoice) => - this.handleSubscriptionUpdate(identity, invoice) + this.handleSubscriptionUpdate(userIdentity, invoice) ) - .with({ billing_reason: 'manual' }, (invoice) => this.topupHandler(invoice, identity)) + .with({ billing_reason: 'manual' }, (invoice) => this.topupHandler(invoice, userIdentity)) .otherwise((invoice) => { this.logger.warn('Unhandled billing reason', { invoiceId: invoice.id, @@ -77,33 +84,41 @@ export class InvoicePaymentSucceededHandler }); } - private createHandler(invoice: Stripe.Invoice, identity: Identity): Promise { + private createHandler( + invoice: Stripe.Invoice, + userIdentity: UserIdentity + ): Promise { const firstConceptInInvoice = invoice.lines.data[0]; return this.extractProduct(firstConceptInInvoice, this.tiers).then( (tierId) => this.subscriptionService - .create(identity, tierId) + .create(userIdentity, tierId) .then((r) => this.creditAdditionHandler(r)), (error) => this.errorHandler('create-subscription')(error) ); } - private renewHandler(invoice: Stripe.Invoice, identity: Identity): Promise { + private renewHandler( + invoice: Stripe.Invoice, + userIdentity: UserIdentity + ): Promise { const firstConceptInInvoice = invoice.lines.data[0]; return this.extractProduct(firstConceptInInvoice, this.tiers).then( (tierId) => - this.subscriptionService.renew(identity, tierId).then((r) => this.creditAdditionHandler(r)), + this.subscriptionService + .renew(userIdentity, tierId) + .then((r) => this.creditAdditionHandler(r)), (error) => this.errorHandler('renew-subscription')(error) ); } private handleSubscriptionUpdate( - identity: Identity, + userIdentity: UserIdentity, invoice: Stripe.Invoice ): Promise { const updateType = this.determineUpdateType(invoice); return this.extractUpdateTiers(invoice).then( - (tiers) => this.executeSubscriptionUpdate(identity, invoice, tiers, updateType), + (tiers) => this.executeSubscriptionUpdate(userIdentity, invoice, tiers, updateType), (error) => Promise.reject( new Error( @@ -147,7 +162,7 @@ export class InvoicePaymentSucceededHandler } private async executeSubscriptionUpdate( - identity: Identity, + userIdentity: UserIdentity, invoice: Stripe.Invoice, tiers: { previousTier: TierId; currentTier: TierId }, updateType: 'upgrade-subscription' | 'downgrade-subscription' | 'undetermined' @@ -157,7 +172,7 @@ export class InvoicePaymentSucceededHandler return this.calculateRemainingCyclePercentageFromInvoice(invoice) .then((remainingPercentage) => this.subscriptionService.upgrade( - identity, + userIdentity, tiers.previousTier, tiers.currentTier, remainingPercentage @@ -170,7 +185,7 @@ export class InvoicePaymentSucceededHandler }) .with('downgrade-subscription', () => this.subscriptionService - .scheduleDowngrade(identity) + .scheduleDowngrade(userIdentity) .catch((error) => this.errorHandler('downgrade-subscription')(error)) ) .with('undetermined', () => @@ -183,7 +198,10 @@ export class InvoicePaymentSucceededHandler .exhaustive(); } - private topupHandler(invoice: Stripe.Invoice, identity: Identity): Promise { + private topupHandler( + invoice: Stripe.Invoice, + userIdentity: UserIdentity + ): Promise { const product = invoice.lines.data[0]; const quantity = product?.quantity || 0; if (quantity <= 0) { @@ -193,7 +211,7 @@ export class InvoicePaymentSucceededHandler } return this.extractProduct(product, this.topups).then( (topupId) => - this.topupService.add(identity, topupId, quantity).then( + this.topupService.add(userIdentity, topupId, quantity).then( (r) => this.creditAdditionHandler(r), (error) => this.errorHandler('topup')(error) ), @@ -258,14 +276,14 @@ export class InvoicePaymentFailedHandler public handle( event: Stripe.InvoicePaymentFailedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const invoice = event.data.object; this.logger.info('Handling invoice payment failed', { invoiceId: invoice.id, customerId: invoice.customer, amount: invoice.amount_due, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/payment-intent.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/payment-intent.ts index f92488177..461e9c7db 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/payment-intent.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/payment-intent.ts @@ -1,5 +1,5 @@ import type { Logger } from '@aws-lambda-powertools/logger'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { Stripe } from 'stripe'; import type { StripeEventType } from '../stripe-schemas'; import { BaseHandler } from './base-handler'; @@ -18,14 +18,14 @@ export class PaymentIntentSucceededHandler public handle( event: Stripe.PaymentIntentSucceededEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const paymentIntent = event.data.object; this.logger.info('Handling payment intent succeeded', { paymentIntentId: paymentIntent.id, customerId: paymentIntent.customer, amount: paymentIntent.amount, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -44,14 +44,14 @@ export class PaymentIntentFailedHandler public handle( event: Stripe.PaymentIntentPaymentFailedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const paymentIntent = event.data.object; this.logger.info('Handling payment intent failed', { paymentIntentId: paymentIntent.id, customerId: paymentIntent.customer, amount: paymentIntent.amount, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.test.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.test.ts index 52d4023f9..87a08e98d 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.test.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.test.ts @@ -1,12 +1,12 @@ import { logger } from '@common/powertools'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { SubscriptionService } from '@services/subscription'; import type Stripe from 'stripe'; import { describe, expect, it, vi } from 'vitest'; import type { StripeEventType } from '../stripe-schemas'; import { SubscriptionDeletedHandler, SubscriptionUpdatedHandler } from './subscription'; -const validIdentity = { userId: 'user-123' } as Identity; +const validIdentity = { userId: 'user-123' } as UserIdentity; const baseSubscription = { id: 'sub_123', customer: 'cus_456', diff --git a/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.ts b/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.ts index 55c2d3594..54de4a61e 100644 --- a/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.ts +++ b/src/lambdas/sqs/stripe-webhook/event-handlers/subscription.ts @@ -1,5 +1,5 @@ import type { Logger } from '@aws-lambda-powertools/logger'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { SubscriptionService } from '@services/subscription'; import type Stripe from 'stripe'; import type { StripeEventType } from '../stripe-schemas'; @@ -19,14 +19,14 @@ export class SubscriptionCreatedHandler public handle( event: Stripe.CustomerSubscriptionCreatedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const subscription = event.data.object; this.logger.info('Handling subscription created', { subscriptionId: subscription.id, customerId: subscription.customer, status: subscription.status, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -46,7 +46,7 @@ export class SubscriptionUpdatedHandler public handle( event: Stripe.CustomerSubscriptionUpdatedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const subscription = event.data.object; const previousAttributes = event.data.previous_attributes; @@ -55,7 +55,7 @@ export class SubscriptionUpdatedHandler customerId: subscription.customer, status: subscription.status, updatedFields: Object.keys(previousAttributes || {}), - userId: identity.userId + userId: userIdentity.userId }); // Docs: https://docs.stripe.com/billing/subscriptions/overview#handle-recurring-charge-failures // Docs2: https://docs.stripe.com/billing/collection-method?locale=en-GB#failed-incomplete-subscriptions @@ -69,7 +69,7 @@ export class SubscriptionUpdatedHandler ]; if (subscriptionStatuses.includes(subscription.status)) { return this.subscriptionService - .cancel(identity, 'unpaid') + .cancel(userIdentity, 'unpaid') .then(() => {}, this.handleError('subscription-unpaid')); } return Promise.resolve(); @@ -90,16 +90,16 @@ export class SubscriptionDeletedHandler public handle( event: Stripe.CustomerSubscriptionDeletedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const subscription = event.data.object; this.logger.info('Handling subscription deleted', { subscriptionId: subscription.id, customerId: subscription.customer, - userId: identity.userId + userId: userIdentity.userId }); return this.subscriptionService - .cancel(identity, 'cancelled') + .cancel(userIdentity, 'cancelled') .then(() => {}, this.handleError('subscription-cancelled')); } } @@ -117,13 +117,13 @@ export class SubscriptionPausedHandler public handle( event: Stripe.CustomerSubscriptionPausedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const subscription = event.data.object; this.logger.info('Handling subscription paused', { subscriptionId: subscription.id, customerId: subscription.customer, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } @@ -142,13 +142,13 @@ export class SubscriptionResumedHandler public handle( event: Stripe.CustomerSubscriptionResumedEvent, - identity: Identity + userIdentity: UserIdentity ): Promise { const subscription = event.data.object; this.logger.info('Handling subscription resumed', { subscriptionId: subscription.id, customerId: subscription.customer, - userId: identity.userId + userId: userIdentity.userId }); return Promise.resolve(); } diff --git a/src/lambdas/sqs/stripe-webhook/event-publisher.test.ts b/src/lambdas/sqs/stripe-webhook/event-publisher.test.ts index 7f744bac6..1b07d6c67 100644 --- a/src/lambdas/sqs/stripe-webhook/event-publisher.test.ts +++ b/src/lambdas/sqs/stripe-webhook/event-publisher.test.ts @@ -3,7 +3,7 @@ import { fromStripeEvent, type PaymentWebhookFiredEvent } from '@model/app-events/StripeWebhookEventFiredEvent'; -import type { Email, Identity, IdpId, IdpName, UserId } from '@notifycal/shared/types'; +import type { Email, IdpId, IdpName, UserId, UserIdentity } from '@notifycal/shared/types'; import type { SnsService } from '@services/sns'; import type Stripe from 'stripe'; import { describe, expect, it, vi } from 'vitest'; @@ -12,7 +12,7 @@ import { StripeEventPublisher } from './event-publisher'; vi.mock('@model/app-events/StripeWebhookEventFiredEvent'); describe(StripeEventPublisher, () => { - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: 'user-123' as UserId, idp: 'google' as IdpName, idpId: 'google-id-123' as IdpId, diff --git a/src/lambdas/sqs/stripe-webhook/event-publisher.ts b/src/lambdas/sqs/stripe-webhook/event-publisher.ts index 834b1d381..4badcb006 100644 --- a/src/lambdas/sqs/stripe-webhook/event-publisher.ts +++ b/src/lambdas/sqs/stripe-webhook/event-publisher.ts @@ -1,17 +1,17 @@ import { fromStripeEvent } from '@model/app-events/StripeWebhookEventFiredEvent'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import type { SnsService } from '@services/sns'; import type Stripe from 'stripe'; export interface EventPublisher { - publish(event: T, identity: Identity): Promise; + publish(event: T, userIdentity: UserIdentity): Promise; } export class StripeEventPublisher implements EventPublisher { public constructor(private readonly snsService: SnsService) {} - public async publish(event: Stripe.Event, identity: Identity): Promise { - const notifycalEvent = fromStripeEvent(event, identity); + public async publish(event: Stripe.Event, userIdentity: UserIdentity): Promise { + const notifycalEvent = fromStripeEvent(event, userIdentity); await this.snsService.safePublish(notifycalEvent); } } diff --git a/src/lambdas/sqs/stripe-webhook/record-processor.ts b/src/lambdas/sqs/stripe-webhook/record-processor.ts index 54818f190..ccdc79854 100644 --- a/src/lambdas/sqs/stripe-webhook/record-processor.ts +++ b/src/lambdas/sqs/stripe-webhook/record-processor.ts @@ -34,10 +34,10 @@ import { SubscriptionUpdatedHandler } from './event-handlers/subscription'; import { StripeEventPublisher } from './event-publisher'; -import { StripeIdentityExtractor } from './identity-extractor'; import type { Record as SqsRecord } from './schema'; import { StripeEventProcessor } from './stripe-event-processor'; import type { StripeEventType } from './stripe-schemas'; +import { StripeUserIdentityExtractor } from './user-identity-extractor'; export function defaultEventHandlers( subscriptionService: SubscriptionService, @@ -119,7 +119,7 @@ export function recordProcessor( const topupService = new TopupService(creditsService, toProductToCreditsMap(topups), snsService); const ourHandlers = eventHandlerFactory(subscriptionService, topupService, tiers, topups, logger); const processor = new StripeEventProcessor( - new StripeIdentityExtractor(userPaymentIndexStore, logger), + new StripeUserIdentityExtractor(userPaymentIndexStore, logger), ourHandlers, new StripeEventPublisher(snsService), logger, diff --git a/src/lambdas/sqs/stripe-webhook/stripe-event-processor.test.ts b/src/lambdas/sqs/stripe-webhook/stripe-event-processor.test.ts index 175ddcbed..8e5c57731 100644 --- a/src/lambdas/sqs/stripe-webhook/stripe-event-processor.test.ts +++ b/src/lambdas/sqs/stripe-webhook/stripe-event-processor.test.ts @@ -1,18 +1,18 @@ /* eslint-disable vitest/max-expects */ /* eslint-disable camelcase */ import type { Logger } from '@aws-lambda-powertools/logger'; -import type { Email, Identity, IdpId, IdpName, UserId } from '@notifycal/shared/types'; +import type { Email, IdpId, IdpName, UserId, UserIdentity } from '@notifycal/shared/types'; import type { Stripe } from 'stripe'; import { v4 } from 'uuid'; import { describe, expect, it, vi } from 'vitest'; import type { EventHandler, EventHandlerBuilder } from './event-handlers/common'; import type { EventPublisher } from './event-publisher'; -import type { IdentityExtractor } from './identity-extractor'; import { StripeEventProcessor } from './stripe-event-processor'; import type { StripeEventType } from './stripe-schemas'; +import type { UserIdentityExtractor } from './user-identity-extractor'; describe(StripeEventProcessor, () => { - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: v4() as UserId, idp: 'google.com', idpId: 'google-id-123' as IdpId, @@ -91,8 +91,8 @@ describe(StripeEventProcessor, () => { expect(publishFn).not.toHaveBeenCalled(); }); - it('should throw error when identity extraction fails', async () => { - const extractionError = new Error('Failed to extract identity'); + it('should throw error when user identity extraction fails', async () => { + const extractionError = new Error('Failed to extract user identity'); const result = testIt(validEvent, () => Promise.reject(extractionError)); @@ -150,7 +150,7 @@ describe(StripeEventProcessor, () => { const identityExtractorMock = { extract: extractFn - } as unknown as IdentityExtractor; + } as unknown as UserIdentityExtractor; const eventHandlerMock = { handle: handleFn diff --git a/src/lambdas/sqs/stripe-webhook/stripe-event-processor.ts b/src/lambdas/sqs/stripe-webhook/stripe-event-processor.ts index 86afb92bf..be039d5c7 100644 --- a/src/lambdas/sqs/stripe-webhook/stripe-event-processor.ts +++ b/src/lambdas/sqs/stripe-webhook/stripe-event-processor.ts @@ -4,12 +4,12 @@ import { tap } from '@utils/promises'; import type { Stripe } from 'stripe'; import type { EventHandler } from './event-handlers/common'; import type { EventPublisher } from './event-publisher'; -import type { IdentityExtractor } from './identity-extractor'; import type { StripeEventType } from './stripe-schemas'; +import type { UserIdentityExtractor } from './user-identity-extractor'; export class StripeEventProcessor { public constructor( - private readonly identityExtractor: IdentityExtractor, + private readonly identityExtractor: UserIdentityExtractor, private readonly eventHandlers: Map< StripeEventType, (type: StripeEventType) => EventHandler @@ -34,22 +34,22 @@ export class StripeEventProcessor { return this.identityExtractor .extract(event) .then( - tap((identity) => { + tap((userIdentity) => { this.logger.appendKeys({ - ...identity + ...userIdentity }); }) ) - .then((identity) => + .then((userIdentity) => handlerFn(event.type as StripeEventType) - .handle(event, identity) + .handle(event, userIdentity) .then( tap(() => { this.logger.info('Successfully processed event'); }) ) .then(() => - this.eventPublisher.publish(event, identity).catch((error) => { + this.eventPublisher.publish(event, userIdentity).catch((error) => { this.logger.error( `There was an error publishing an Stripe event after having processed it`, { cause: error, event } diff --git a/src/lambdas/sqs/stripe-webhook/identity-extractor.test.ts b/src/lambdas/sqs/stripe-webhook/user-identity-extractor.test.ts similarity index 91% rename from src/lambdas/sqs/stripe-webhook/identity-extractor.test.ts rename to src/lambdas/sqs/stripe-webhook/user-identity-extractor.test.ts index 1fdc36e18..5350adb7f 100644 --- a/src/lambdas/sqs/stripe-webhook/identity-extractor.test.ts +++ b/src/lambdas/sqs/stripe-webhook/user-identity-extractor.test.ts @@ -3,19 +3,19 @@ import type { Logger } from '@aws-lambda-powertools/logger'; import type { PaymentUserStoreRecord } from '@model/store/UserPaymentStoreRecord'; import type { Email, - Identity, IdpId, IdpName, StripeCustomerId, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import type { PaymentUserIndexStore } from '@services/stores/payment-user-index-store'; import type { Stripe } from 'stripe'; import { v4 } from 'uuid'; import { describe, expect, it, vi } from 'vitest'; -import { StripeIdentityExtractor } from './identity-extractor'; +import { StripeUserIdentityExtractor } from './user-identity-extractor'; -describe(StripeIdentityExtractor, () => { +describe(StripeUserIdentityExtractor, () => { const validStripeCustomerId = 'cus_123' as StripeCustomerId; const validUserId = v4() as UserId; const validIdp = 'google.com'; @@ -30,7 +30,7 @@ describe(StripeIdentityExtractor, () => { Email: validEmail }; - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: validUserId, idp: validIdp, idpId: validIdpId, @@ -87,7 +87,7 @@ describe(StripeIdentityExtractor, () => { } as unknown as Stripe.Checkout.Session; describe('customer events', () => { - it('should extract identity from customer.created event', async () => { + it('should extract user identity from customer.created event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -108,7 +108,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.updated event', async () => { + it('should extract user identity from customer.updated event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -129,7 +129,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.deleted event', async () => { + it('should extract user identity from customer.deleted event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -152,7 +152,7 @@ describe(StripeIdentityExtractor, () => { }); describe('subscription events', () => { - it('should extract identity from customer.subscription.created event', async () => { + it('should extract user identity from customer.subscription.created event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -173,7 +173,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.subscription.updated event', async () => { + it('should extract user identity from customer.subscription.updated event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -194,7 +194,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.subscription.deleted event', async () => { + it('should extract user identity from customer.subscription.deleted event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -215,7 +215,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.subscription.paused event', async () => { + it('should extract user identity from customer.subscription.paused event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -236,7 +236,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from customer.subscription.resumed event', async () => { + it('should extract user identity from customer.subscription.resumed event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -259,7 +259,7 @@ describe(StripeIdentityExtractor, () => { }); describe('invoice events', () => { - it('should extract identity from invoice.created event', async () => { + it('should extract user identity from invoice.created event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -280,7 +280,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from invoice.payment_succeeded event', async () => { + it('should extract user identity from invoice.payment_succeeded event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -301,7 +301,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from invoice.payment_failed event', async () => { + it('should extract user identity from invoice.payment_failed event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -324,7 +324,7 @@ describe(StripeIdentityExtractor, () => { }); describe('payment intent events', () => { - it('should extract identity from payment_intent.succeeded event', async () => { + it('should extract user identity from payment_intent.succeeded event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -345,7 +345,7 @@ describe(StripeIdentityExtractor, () => { expect(result).toStrictEqual(validIdentity); }); - it('should extract identity from payment_intent.payment_failed event', async () => { + it('should extract user identity from payment_intent.payment_failed event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -368,7 +368,7 @@ describe(StripeIdentityExtractor, () => { }); describe('checkout session events', () => { - it('should extract identity from checkout.session.completed event', async () => { + it('should extract user identity from checkout.session.completed event', async () => { const event: Stripe.Event = { id: 'evt_123', object: 'event', @@ -568,9 +568,9 @@ describe(StripeIdentityExtractor, () => { function testIt( event: Stripe.Event, - getPaymentUserByStripeCustomerIdFn: () => Promise | null | undefined>, + getPaymentUserByStripeCustomerIdFn: () => Promise | null | undefined>, errorLoggerFn: () => void - ): Promise> { + ): Promise> { const userPaymentIndexStoreMock = { getPaymentUserByStripeCustomerId: getPaymentUserByStripeCustomerIdFn } as unknown as PaymentUserIndexStore; @@ -579,7 +579,7 @@ describe(StripeIdentityExtractor, () => { error: errorLoggerFn } as unknown as Logger; - const extractor = new StripeIdentityExtractor(userPaymentIndexStoreMock, loggerMock); + const extractor = new StripeUserIdentityExtractor(userPaymentIndexStoreMock, loggerMock); return extractor.extract(event); } }); diff --git a/src/lambdas/sqs/stripe-webhook/identity-extractor.ts b/src/lambdas/sqs/stripe-webhook/user-identity-extractor.ts similarity index 84% rename from src/lambdas/sqs/stripe-webhook/identity-extractor.ts rename to src/lambdas/sqs/stripe-webhook/user-identity-extractor.ts index 21cba3b04..4b6819690 100644 --- a/src/lambdas/sqs/stripe-webhook/identity-extractor.ts +++ b/src/lambdas/sqs/stripe-webhook/user-identity-extractor.ts @@ -1,20 +1,20 @@ import type { Logger } from '@aws-lambda-powertools/logger'; -import { extractIdentity } from '@model/UserIdentity'; -import type { Identity, IdpName, StripeCustomerId } from '@notifycal/shared/types'; +import { extractUserIdentity } from '@model/UserIdentity'; +import type { IdpName, StripeCustomerId, UserIdentity } from '@notifycal/shared/types'; import type { PaymentUserIndexStore } from '@services/stores/payment-user-index-store'; import type { Stripe } from 'stripe'; import { match, P } from 'ts-pattern'; -export interface IdentityExtractor { - extract(event: T): Promise>; +export interface UserIdentityExtractor { + extract(event: T): Promise>; } -export class StripeIdentityExtractor implements IdentityExtractor { +export class StripeUserIdentityExtractor implements UserIdentityExtractor { public constructor( private readonly userPaymentIndexStore: PaymentUserIndexStore, private readonly logger: Logger ) {} - public extract(event: Stripe.Event): Promise> { + public extract(event: Stripe.Event): Promise> { const stripeCustomerId = this.getStripeCustomerId(event); if (!stripeCustomerId) { @@ -26,7 +26,7 @@ export class StripeIdentityExtractor implements IdentityExtractor return this.userPaymentIndexStore.getPaymentUserByStripeCustomerId(stripeCustomerId).then( (paymentUser) => { if (paymentUser) { - return extractIdentity(paymentUser); + return extractUserIdentity(paymentUser); } else { return Promise.reject( new Error( diff --git a/src/model/LiveUser.ts b/src/model/LiveUser.ts index b22c313df..a71bb9033 100644 --- a/src/model/LiveUser.ts +++ b/src/model/LiveUser.ts @@ -2,9 +2,9 @@ import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import type { DateTime, - Identity, IdpName, - ReminderConfigTransformed + ReminderConfigTransformed, + UserIdentity } from '@notifycal/shared/types'; export interface UserCalendar { @@ -20,7 +20,7 @@ export interface LiveUserConfig extends Omit extends Identity { +export interface LiveUser extends UserIdentity { config: ReminderConfigTransformed; idpAuthorization: AuthorizationForIdp; } diff --git a/src/model/UserIdentity.ts b/src/model/UserIdentity.ts index 2365a3188..51354c0c3 100644 --- a/src/model/UserIdentity.ts +++ b/src/model/UserIdentity.ts @@ -1,10 +1,10 @@ -import type { CapitalizeKeys, Identity, IdpName } from '@notifycal/shared/types'; +import type { CapitalizeKeys, IdpName, UserIdentity } from '@notifycal/shared/types'; -export type UserIdentity = CapitalizeKeys>; +export type UserIdentityStoreRecord = CapitalizeKeys>; -export function extractIdentity( - user: UserIdentity -): Identity { +export function extractUserIdentity( + user: UserIdentityStoreRecord +): UserIdentity { return { userId: user.UserId, email: user.Email, diff --git a/src/model/app-events/EmailToBeSentEvent.ts b/src/model/app-events/EmailToBeSentEvent.ts index 5a43da3ad..bde959901 100644 --- a/src/model/app-events/EmailToBeSentEvent.ts +++ b/src/model/app-events/EmailToBeSentEvent.ts @@ -49,12 +49,12 @@ export const emailToBeSentEventSchema = eventSchemaGenerator('EmailToBeSent', da export type EmailToBeSentEvent = z.infer; export function emailToBeSent( - identity: EventSourceIdentity, + userIdentity: EventSourceIdentity, data: EmailToBeSentEvent['data'], options: EventCreationOptions ): EmailToBeSentEvent { return { - ...createEventBase('EmailToBeSent', identity, options), + ...createEventBase('EmailToBeSent', userIdentity, options), data }; } diff --git a/src/model/app-events/NoUserCalendarFoundEvent.ts b/src/model/app-events/NoUserCalendarFoundEvent.ts index 57162852b..a95a3e620 100644 --- a/src/model/app-events/NoUserCalendarFoundEvent.ts +++ b/src/model/app-events/NoUserCalendarFoundEvent.ts @@ -1,5 +1,5 @@ import type { Record } from '@lambdas/sqs/fetch-user-calendars/index'; -import type { CorrelationId, EventId, Identity, IdpName } from '@notifycal/shared/types'; +import type { CorrelationId, EventId, IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase, eventIdSchema, runSchema } from './common'; @@ -15,10 +15,10 @@ export type NoUserCalendarFoundEvent = z.infer, - identity: Identity + userIdentity: UserIdentity ): NoUserCalendarFoundEvent { return { - ...createEventBase('NoUserCalendarFound', identity, { + ...createEventBase('NoUserCalendarFound', userIdentity, { correlationId: origin.id as CorrelationId }), data: { diff --git a/src/model/app-events/StripeWebhookEventFiredEvent.ts b/src/model/app-events/StripeWebhookEventFiredEvent.ts index dc400061c..26509b6d6 100644 --- a/src/model/app-events/StripeWebhookEventFiredEvent.ts +++ b/src/model/app-events/StripeWebhookEventFiredEvent.ts @@ -1,5 +1,5 @@ import { stripeEventTypes } from '@lambdas/sqs/stripe-webhook/stripe-schemas'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { toPascalCase } from '@utils/case'; import type { CapitalizeFirst, ReplaceUnderscoreWithDot, SplitByDot } from '@utils/types'; import type Stripe from 'stripe'; @@ -53,11 +53,11 @@ export const ourStripeEventTypeZodLiteralArray = stripeEventTypes.map((type) => export function fromStripeEvent( origin: Stripe.Event, - identity: Identity + userIdentity: UserIdentity ): PaymentWebhookFiredEvent { const stripeEventType = origin.type; return { - ...createEventBase(toOurEventType(stripeEventType), identity), + ...createEventBase(toOurEventType(stripeEventType), userIdentity), data: { ...origin } diff --git a/src/model/app-events/SubscriptionCancellationFailedEvent.ts b/src/model/app-events/SubscriptionCancellationFailedEvent.ts index 044171ccd..d647ecdbf 100644 --- a/src/model/app-events/SubscriptionCancellationFailedEvent.ts +++ b/src/model/app-events/SubscriptionCancellationFailedEvent.ts @@ -1,5 +1,5 @@ import type { CreditDeductionResult } from '@model/Credits'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -19,13 +19,13 @@ export type SubscriptionCancellationFailedEvent = z.infer< >; export function subscriptionCancellationFailedEvent( - identity: Identity, + userIdentity: UserIdentity, reason: 'unpaid' | 'cancelled', result?: CreditDeductionResult, error?: unknown ): SubscriptionCancellationFailedEvent { return { - ...createEventBase('SubscriptionCancellationFailed', identity), + ...createEventBase('SubscriptionCancellationFailed', userIdentity), data: { reason, result, diff --git a/src/model/app-events/SubscriptionCancelledEvent.ts b/src/model/app-events/SubscriptionCancelledEvent.ts index e588d52b9..a5b8c6e24 100644 --- a/src/model/app-events/SubscriptionCancelledEvent.ts +++ b/src/model/app-events/SubscriptionCancelledEvent.ts @@ -1,5 +1,5 @@ import type { CreditDeductionResult } from '@model/Credits'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,12 +15,12 @@ export type SubscriptionCancelledEventData = z.infer; export function subscriptionCancelledEvent( - identity: Identity, + userIdentity: UserIdentity, reason: 'unpaid' | 'cancelled', result: CreditDeductionResult ): SubscriptionCancelledEvent { return { - ...createEventBase('SubscriptionCancelled', identity), + ...createEventBase('SubscriptionCancelled', userIdentity), data: { reason, result diff --git a/src/model/app-events/SubscriptionCreatedEvent.ts b/src/model/app-events/SubscriptionCreatedEvent.ts index 4e1269500..30b57eab5 100644 --- a/src/model/app-events/SubscriptionCreatedEvent.ts +++ b/src/model/app-events/SubscriptionCreatedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TierId } from '@notifycal/shared/types'; +import type { IdpName, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,12 +15,12 @@ export type SubscriptionCreatedEventData = z.infer; export function subscriptionCreatedEvent( - identity: Identity, + userIdentity: UserIdentity, tier: TierId, result: CreditAdditionResult ): SubscriptionCreatedEvent { return { - ...createEventBase('SubscriptionCreated', identity), + ...createEventBase('SubscriptionCreated', userIdentity), data: { result, tier diff --git a/src/model/app-events/SubscriptionCreationFailedEvent.ts b/src/model/app-events/SubscriptionCreationFailedEvent.ts index f2cc7ca7b..0febe308e 100644 --- a/src/model/app-events/SubscriptionCreationFailedEvent.ts +++ b/src/model/app-events/SubscriptionCreationFailedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TierId } from '@notifycal/shared/types'; +import type { IdpName, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -17,13 +17,13 @@ export type SubscriptionCreationFailedEventData = z.infer< export type SubscriptionCreationFailedEvent = z.infer; export function subscriptionCreationFailedEvent( - identity: Identity, + userIdentity: UserIdentity, tier: TierId, result?: CreditAdditionResult, error?: unknown ): SubscriptionCreationFailedEvent { return { - ...createEventBase('SubscriptionCreationFailed', identity), + ...createEventBase('SubscriptionCreationFailed', userIdentity), data: { tier, result, diff --git a/src/model/app-events/SubscriptionDowngradeScheduledEvent.ts b/src/model/app-events/SubscriptionDowngradeScheduledEvent.ts index 884b8699a..bb718ac08 100644 --- a/src/model/app-events/SubscriptionDowngradeScheduledEvent.ts +++ b/src/model/app-events/SubscriptionDowngradeScheduledEvent.ts @@ -1,4 +1,4 @@ -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -18,10 +18,10 @@ export type SubscriptionDowngradeScheduledEvent = z.infer< >; export function subscriptionDowngradeScheduledEvent( - identity: Identity + userIdentity: UserIdentity ): SubscriptionDowngradeScheduledEvent { return { - ...createEventBase('SubscriptionDowngradeScheduled', identity), + ...createEventBase('SubscriptionDowngradeScheduled', userIdentity), data: {} }; } diff --git a/src/model/app-events/SubscriptionRenewalFailedEvent.ts b/src/model/app-events/SubscriptionRenewalFailedEvent.ts index ae57ffa4d..bae7dfb9a 100644 --- a/src/model/app-events/SubscriptionRenewalFailedEvent.ts +++ b/src/model/app-events/SubscriptionRenewalFailedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TierId } from '@notifycal/shared/types'; +import type { IdpName, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -17,13 +17,13 @@ export type SubscriptionRenewalFailedEventData = z.infer< export type SubscriptionRenewalFailedEvent = z.infer; export function subscriptionRenewalFailedEvent( - identity: Identity, + userIdentity: UserIdentity, tier: TierId, result?: CreditAdditionResult, error?: unknown ): SubscriptionRenewalFailedEvent { return { - ...createEventBase('SubscriptionRenewalFailed', identity), + ...createEventBase('SubscriptionRenewalFailed', userIdentity), data: { tier, result, diff --git a/src/model/app-events/SubscriptionRenewedEvent.ts b/src/model/app-events/SubscriptionRenewedEvent.ts index c450ff41c..5aa064e8c 100644 --- a/src/model/app-events/SubscriptionRenewedEvent.ts +++ b/src/model/app-events/SubscriptionRenewedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TierId } from '@notifycal/shared/types'; +import type { IdpName, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,12 +15,12 @@ export type SubscriptionRenewedEventData = z.infer; export function subscriptionRenewedEvent( - identity: Identity, + userIdentity: UserIdentity, tier: TierId, result: CreditAdditionResult ): SubscriptionRenewedEvent { return { - ...createEventBase('SubscriptionRenewed', identity), + ...createEventBase('SubscriptionRenewed', userIdentity), data: { tier, result diff --git a/src/model/app-events/SubscriptionUpgradeFailedEvent.ts b/src/model/app-events/SubscriptionUpgradeFailedEvent.ts index 717202c20..54158ff94 100644 --- a/src/model/app-events/SubscriptionUpgradeFailedEvent.ts +++ b/src/model/app-events/SubscriptionUpgradeFailedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, Percentage, TierId } from '@notifycal/shared/types'; +import type { IdpName, Percentage, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -17,7 +17,7 @@ export type SubscriptionUpgradeFailedEventData = z.infer< export type SubscriptionUpgradeFailedEvent = z.infer; export function subscriptionUpgradeFailedEvent( - identity: Identity, + userIdentity: UserIdentity, previousTier: TierId, currentTier: TierId, remainingPercentage?: Percentage, @@ -26,7 +26,7 @@ export function subscriptionUpgradeFailedEvent( error?: unknown ): SubscriptionUpgradeFailedEvent { return { - ...createEventBase('SubscriptionUpgradeFailed', identity), + ...createEventBase('SubscriptionUpgradeFailed', userIdentity), data: { previousTier, currentTier, diff --git a/src/model/app-events/SubscriptionUpgradedEvent.ts b/src/model/app-events/SubscriptionUpgradedEvent.ts index f8ad92216..12b41df4b 100644 --- a/src/model/app-events/SubscriptionUpgradedEvent.ts +++ b/src/model/app-events/SubscriptionUpgradedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, Percentage, TierId } from '@notifycal/shared/types'; +import type { IdpName, Percentage, TierId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,7 +15,7 @@ export type SubscriptionUpgradedEventData = z.infer; export function subscriptionUpgradedEvent( - identity: Identity, + userIdentity: UserIdentity, previousTier: TierId, currentTier: TierId, remainingPercentage: Percentage, @@ -23,7 +23,7 @@ export function subscriptionUpgradedEvent( result: CreditAdditionResult ): SubscriptionUpgradedEvent { return { - ...createEventBase('SubscriptionUpgraded', identity), + ...createEventBase('SubscriptionUpgraded', userIdentity), data: { previousTier, currentTier, diff --git a/src/model/app-events/TopupFailedEvent.ts b/src/model/app-events/TopupFailedEvent.ts index a1690f7d6..df6b45e69 100644 --- a/src/model/app-events/TopupFailedEvent.ts +++ b/src/model/app-events/TopupFailedEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TopupId } from '@notifycal/shared/types'; +import type { IdpName, TopupId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,7 +15,7 @@ export type TopupFailedEventData = z.infer; export type TopupFailedEvent = z.infer; export function topupFailedEvent( - identity: Identity, + userIdentity: UserIdentity, topupId: TopupId, quantity: number, credits: number, @@ -23,7 +23,7 @@ export function topupFailedEvent( error: unknown ): TopupFailedEvent { return { - ...createEventBase('TopupFailed', identity), + ...createEventBase('TopupFailed', userIdentity), data: { topupId, quantity, diff --git a/src/model/app-events/TopupSucceededEvent.ts b/src/model/app-events/TopupSucceededEvent.ts index b428e856e..c03a294c8 100644 --- a/src/model/app-events/TopupSucceededEvent.ts +++ b/src/model/app-events/TopupSucceededEvent.ts @@ -1,5 +1,5 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TopupId } from '@notifycal/shared/types'; +import type { IdpName, TopupId, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -15,14 +15,14 @@ export type TopupSucceededEventData = z.infer; export function topupSucceededEvent( - identity: Identity, + userIdentity: UserIdentity, topupId: TopupId, quantity: number, credits: number, result: CreditAdditionResult ): TopupSucceededEvent { return { - ...createEventBase('TopupSucceeded', identity), + ...createEventBase('TopupSucceeded', userIdentity), data: { topupId, quantity, diff --git a/src/model/app-events/UserSignInFailedEvent.ts b/src/model/app-events/UserSignInFailedEvent.ts index 304fd1f45..99dc070bc 100644 --- a/src/model/app-events/UserSignInFailedEvent.ts +++ b/src/model/app-events/UserSignInFailedEvent.ts @@ -1,6 +1,6 @@ import type { UserStoreRecord } from '@model/store/UserStoreRecord'; import { unixTimestampSchema } from '@notifycal/shared/schemas'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -13,11 +13,11 @@ export const userSignInFailedEventSchema = errorEventSchemaGenerator('UserSignIn export type UserSignInFailedEvent = z.infer; export function userSignInFailed( - identity: Identity, + userIdentity: UserIdentity, userBeforeLogin: UserStoreRecord ): UserSignInFailedEvent { return { - ...createEventBase('UserSignInFailed', identity), + ...createEventBase('UserSignInFailed', userIdentity), data: { lastSignInAt: userBeforeLogin.LastSignInAt } diff --git a/src/model/app-events/UserSignUpFailedEvent.ts b/src/model/app-events/UserSignUpFailedEvent.ts index d3dcc8da5..dbded3182 100644 --- a/src/model/app-events/UserSignUpFailedEvent.ts +++ b/src/model/app-events/UserSignUpFailedEvent.ts @@ -1,4 +1,4 @@ -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { errorEventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -9,10 +9,10 @@ export const userSignUpFailedEventSchema = errorEventSchemaGenerator('UserSignUp export type UserSignUpFailedEvent = z.infer; export function userSignUpFailed( - identity: Identity + userIdentity: UserIdentity ): UserSignUpFailedEvent { return { - ...createEventBase('UserSignUpFailed', identity), + ...createEventBase('UserSignUpFailed', userIdentity), data: {} }; } diff --git a/src/model/app-events/UserSignedInEvent.ts b/src/model/app-events/UserSignedInEvent.ts index 6c8763957..de3543c30 100644 --- a/src/model/app-events/UserSignedInEvent.ts +++ b/src/model/app-events/UserSignedInEvent.ts @@ -1,6 +1,6 @@ import type { UserStoreRecord } from '@model/store/UserStoreRecord'; import { unixTimestampSchema } from '@notifycal/shared/schemas'; -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -13,11 +13,11 @@ export const userSignedInEventSchema = eventSchemaGenerator('UserSignInSucceeded export type UserSignedInEvent = z.infer; export function userSignedIn( - identity: Identity, + userIdentity: UserIdentity, userBeforeLogin: UserStoreRecord ): UserSignedInEvent { return { - ...createEventBase('UserSignInSucceeded', identity), + ...createEventBase('UserSignInSucceeded', userIdentity), data: { lastSignInAt: userBeforeLogin.LastSignInAt } diff --git a/src/model/app-events/UserSignedUpEvent.ts b/src/model/app-events/UserSignedUpEvent.ts index 09331ab17..c066c0004 100644 --- a/src/model/app-events/UserSignedUpEvent.ts +++ b/src/model/app-events/UserSignedUpEvent.ts @@ -1,4 +1,4 @@ -import type { Identity, IdpName } from '@notifycal/shared/types'; +import type { IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; import { eventSchemaGenerator } from './BaseEvent'; import { createEventBase } from './common'; @@ -9,10 +9,10 @@ export const userSignedUpEventSchema = eventSchemaGenerator('UserSignUpSucceeded export type UserSignedUpEvent = z.infer; export function userSignedUp( - identity: Identity + userIdentity: UserIdentity ): UserSignedUpEvent { return { - ...createEventBase('UserSignUpSucceeded', identity), + ...createEventBase('UserSignUpSucceeded', userIdentity), data: {} }; } diff --git a/src/model/store/AuditTrailStoreRecord.ts b/src/model/store/AuditTrailStoreRecord.ts index 9fc8be5ec..3e80ebb14 100644 --- a/src/model/store/AuditTrailStoreRecord.ts +++ b/src/model/store/AuditTrailStoreRecord.ts @@ -8,7 +8,7 @@ import { } from '@model/app-events/BaseEvent'; import { eventIdSchema } from '@model/app-events/common'; import { dateTimeSchema, idpIdSchema, userIdSchema } from '@notifycal/shared/schemas'; -import type { Brand, CapitalizeKeys, Identity, IdpName } from '@notifycal/shared/types'; +import type { Brand, CapitalizeKeys, IdpName, UserIdentity } from '@notifycal/shared/types'; import { z } from 'zod'; export const dataSchema = z.object({}).passthrough(); @@ -55,7 +55,7 @@ export function auditTrailStoreRecordSchema< export type AuditTrailStoreRecord = z.infer; -type IdentityNoEmail = Omit, 'email'>; +type IdentityNoEmail = Omit, 'email'>; export function extractIdentity( event: CapitalizeKeys> ): IdentityNoEmail { diff --git a/src/model/store/LiveUserStoreRecord.ts b/src/model/store/LiveUserStoreRecord.ts index 559f6a833..d02e3171d 100644 --- a/src/model/store/LiveUserStoreRecord.ts +++ b/src/model/store/LiveUserStoreRecord.ts @@ -1,11 +1,11 @@ -import { extractIdentity, type UserIdentity } from '@model/UserIdentity'; +import { extractUserIdentity, type UserIdentityStoreRecord } from '@model/UserIdentity'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import type { LiveUser } from '@model/LiveUser'; import type { IdpName, UserStatus } from '@notifycal/shared/types'; import { fromStoreRecord, type ReminderConfigStoreRecord } from './ReminderConfigStoreRecord'; -export interface LiveUserStoreRecord extends UserIdentity { +export interface LiveUserStoreRecord extends UserIdentityStoreRecord { Config: ReminderConfigStoreRecord; IdpAuthorization: AuthorizationForIdp; UserStatus: UserStatus; // Not querying for this, but it'll be included as it's the GSI Hash key @@ -15,7 +15,7 @@ export function extractLiveUser( userRecord: LiveUserStoreRecord ): LiveUser { return { - ...extractIdentity(userRecord), + ...extractUserIdentity(userRecord), config: fromStoreRecord(userRecord.Config), idpAuthorization: userRecord.IdpAuthorization }; diff --git a/src/model/store/UserPaymentStoreRecord.ts b/src/model/store/UserPaymentStoreRecord.ts index 2bbc5e18a..5b223717c 100644 --- a/src/model/store/UserPaymentStoreRecord.ts +++ b/src/model/store/UserPaymentStoreRecord.ts @@ -1,7 +1,7 @@ -import type { UserIdentity } from '@model/UserIdentity'; +import type { UserIdentityStoreRecord } from '@model/UserIdentity'; import type { StripeCustomerId } from '@notifycal/shared/types'; -export interface PaymentUserStoreRecord extends UserIdentity { +export interface PaymentUserStoreRecord extends UserIdentityStoreRecord { StripeCustomerId: StripeCustomerId; } diff --git a/src/model/store/UserStoreRecord.ts b/src/model/store/UserStoreRecord.ts index 04800e8a0..3fad9e640 100644 --- a/src/model/store/UserStoreRecord.ts +++ b/src/model/store/UserStoreRecord.ts @@ -1,4 +1,4 @@ -import { type UserIdentity, extractIdentity } from '@model/UserIdentity'; +import { type UserIdentityStoreRecord, extractUserIdentity } from '@model/UserIdentity'; import type { IdpName, StripeCustomerId, @@ -33,7 +33,7 @@ export type CreditBalanceType = keyof Pick< >; export type UserStoreRecordCredits = Required, 'Credits'>>; -export interface UserStoreRecord extends UserIdentity { +export interface UserStoreRecord extends UserIdentityStoreRecord { LastSignInAt: UnixTimestamp; SignedUpAt: UnixTimestamp; UserStatus: UserStatus; @@ -47,7 +47,7 @@ export function extractUser( userRecord: UserStoreRecord ): User { return { - ...extractIdentity(userRecord), + ...extractUserIdentity(userRecord), lastSignInAt: userRecord.LastSignInAt, signedUpAt: userRecord.SignedUpAt, userStatus: userRecord.UserStatus, diff --git a/src/services/auth.test.ts b/src/services/auth.test.ts index 0bdb5fcf4..295228652 100644 --- a/src/services/auth.test.ts +++ b/src/services/auth.test.ts @@ -12,11 +12,11 @@ import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import type { UserStoreRecord } from '@model/store/UserStoreRecord'; import type { Email, - Identity, IdpId, IdpName, UnixTimestamp, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import type { AwsArn, PrivateKey } from '@own-types/model'; import { validJwts } from '@testing/utils/jwt'; @@ -37,7 +37,7 @@ const validUserId: UserId = uuid() as UserId; const validIdpName: IdpName = 'google.com'; const validEmail = 'test@example.com' as Email; -const validIdentity: Identity<'google.com'> = { +const validIdentity: UserIdentity<'google.com'> = { userId: validUserId, email: validEmail, idp: validIdpName, @@ -271,7 +271,7 @@ describe('Auth Service', () => { }); function testSignInOrUp( - identity: Identity, + userIdentity: UserIdentity, authorization: AuthorizationForIdp, getUserByIdFn: () => Promise | null>, putUserFn: () => Promise, @@ -305,11 +305,11 @@ describe('Auth Service', () => { })); vi.mocked(buildJwts).mockImplementation(buildJwtsFn); - return signInOrUp(identity, authorization, validConfig, logger); + return signInOrUp(userIdentity, authorization, validConfig, logger); } function testBuildJwtsAndStoreRefreshJwt( - identity: Identity, + userIdentity: UserIdentity, encodeAccessJwtConfig: EncodeAccessJwtConfig, encodeRefreshJwtConfig: EncodeRefreshJwtConfig, buildJwtsFn: () => Promise, @@ -328,7 +328,7 @@ describe('Auth Service', () => { vi.spyOn(store, 'putToken').mockImplementation(refreshTokenStoreMock.putToken); return buildJwtsAndStoreRefreshJwt( - identity, + userIdentity, encodeAccessJwtConfig, encodeRefreshJwtConfig, store diff --git a/src/services/auth.ts b/src/services/auth.ts index bbe11a7b4..51a9d18eb 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -11,8 +11,8 @@ import type { } from '@model/Config'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; import type { UserStoreRecord } from '@model/store/UserStoreRecord'; -import { type UserIdentity, extractIdentity } from '@model/UserIdentity'; -import type { Identity, IdpName, UnixTimestamp } from '@notifycal/shared/types'; +import { type UserIdentityStoreRecord, extractUserIdentity } from '@model/UserIdentity'; +import type { IdpName, UnixTimestamp, UserIdentity } from '@notifycal/shared/types'; import { doAndRethrow, tap } from '@utils/promises'; import type { APIGatewayProxyResult } from 'aws-lambda'; import { successHandler } from './common/api-response-handlers'; @@ -23,7 +23,7 @@ import { UserBaseStore } from './stores/user-base-store'; function signIn( user: UserStoreRecord, - identity: Identity, + userIdentity: UserIdentity, authorization: AuthorizationForIdp, userProvider: UserBaseStore ): Promise> { @@ -35,22 +35,22 @@ function signIn( return userProvider.putUser(updatedUser, authorization).then(() => updatedUser); } else { return Promise.reject( - new Error(`User with id '${identity.userId}' is banned and login is prohibited`) + new Error(`User with id '${userIdentity.userId}' is banned and login is prohibited`) ); } } function signUp( - identity: Identity, + userIdentity: UserIdentity, authorization: AuthorizationForIdp, userProvider: UserBaseStore ): Promise> { const now = Date.now() as UnixTimestamp; const newUser: UserStoreRecord = { - UserId: identity.userId, - Email: identity.email, - Idp: identity.idp, - IdpId: identity.idpId, + UserId: userIdentity.userId, + Email: userIdentity.email, + Idp: userIdentity.idp, + IdpId: userIdentity.idpId, LastSignInAt: now, SignedUpAt: now, UserStatus: 'onboarding' @@ -59,12 +59,12 @@ function signUp( } export function buildJwtsAndStoreRefreshJwt( - identity: Identity, + userIdentity: UserIdentity, encodeAccessJwtConfig: EncodeAccessJwtConfig, encodeRefreshJwtConfig: EncodeRefreshJwtConfig, store: RefreshTokenBaseStore ): Promise { - return buildJwts(identity, encodeAccessJwtConfig, encodeRefreshJwtConfig).then((jwts) => { + return buildJwts(userIdentity, encodeAccessJwtConfig, encodeRefreshJwtConfig).then((jwts) => { return store .putToken({ UserId: jwts.refreshToken.decoded.payload.sub, @@ -77,25 +77,25 @@ export function buildJwtsAndStoreRefreshJwt( } function handleFailureToGetUserById( - identity: Identity + userIdentity: UserIdentity ): (reason: unknown) => PromiseLike { return (error) => Promise.reject( new Error( - `Failed to fetch '${identity.userId}' out of persistance. Unable to say if the user was signing in or up as the call to persistance failed`, + `Failed to fetch '${userIdentity.userId}' out of persistance. Unable to say if the user was signing in or up as the call to persistance failed`, { cause: error } ) ); } function generateAuthentication( - user: UserIdentity, + user: UserIdentityStoreRecord, config: BaseLoginConfig, logger: Logger ): Promise { const store = new RefreshTokenBaseStore(config.refreshTokenBaseStoreConfig, logger); return buildJwtsAndStoreRefreshJwt( - extractIdentity(user), + extractUserIdentity(user), config.encodeAccessJwtConfig, config.encodeRefreshJwtConfig, store @@ -103,7 +103,7 @@ function generateAuthentication( } export function signInOrUp( - identity: Identity, + userIdentity: UserIdentity, authorization: AuthorizationForIdp, config: BaseLoginConfig & ApiRestTopicConfig, logger: Logger @@ -111,23 +111,23 @@ export function signInOrUp( const userProvider = UserBaseStore.withConfig(config.userBaseStoreConfig, logger); const snsService = SnsService.withConfig(config.apiRestTopicConfig, logger); - return userProvider.getUserById(identity.userId).then((userOrNot) => { + return userProvider.getUserById(userIdentity.userId).then((userOrNot) => { if (userOrNot) { - return signIn(userOrNot, identity, authorization, userProvider) + return signIn(userOrNot, userIdentity, authorization, userProvider) .then((user) => generateAuthentication(user, config, logger)) .then( - tap(() => snsService.safePublish(userSignedIn(identity, userOrNot))), - doAndRethrow(() => snsService.safePublish(userSignInFailed(identity, userOrNot))) + tap(() => snsService.safePublish(userSignedIn(userIdentity, userOrNot))), + doAndRethrow(() => snsService.safePublish(userSignInFailed(userIdentity, userOrNot))) ); } else { - return signUp(identity, authorization, userProvider) + return signUp(userIdentity, authorization, userProvider) .then((user) => generateAuthentication(user, config, logger)) .then( - tap(() => snsService.safePublish(userSignedUp(identity))), - doAndRethrow(() => snsService.safePublish(userSignUpFailed(identity))) + tap(() => snsService.safePublish(userSignedUp(userIdentity))), + doAndRethrow(() => snsService.safePublish(userSignUpFailed(userIdentity))) ); } - }, handleFailureToGetUserById(identity)); + }, handleFailureToGetUserById(userIdentity)); } export function _successHandler(jwts: EncodedAndDecodedJwts): APIGatewayProxyResult { diff --git a/src/services/email-template-service.ts b/src/services/email-template-service.ts index d502305b7..1d49c81c1 100644 --- a/src/services/email-template-service.ts +++ b/src/services/email-template-service.ts @@ -90,7 +90,7 @@ export class EmailTemplateService { templateConfig: EmailTemplateConfig, subEventType: EmailToBeSentEvent['data']['subEventType'], metadata: Record, - identity: EventSourceIdentity, + userIdentity: EventSourceIdentity, options: EventCreationOptions ): EmailToBeSentEvent { const eventData = this.interpolateEmail( @@ -101,6 +101,6 @@ export class EmailTemplateService { subEventType, metadata ); - return emailToBeSent(identity, eventData, options); + return emailToBeSent(userIdentity, eventData, options); } } diff --git a/src/services/google/oauth.test.ts b/src/services/google/oauth.test.ts index 2f315364d..67f5ee515 100644 --- a/src/services/google/oauth.test.ts +++ b/src/services/google/oauth.test.ts @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import { logger } from '@common/powertools'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; -import type { Identity, Uuid } from '@notifycal/shared/types'; +import type { UserIdentity, Uuid } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { type Credentials, @@ -49,7 +49,7 @@ const validVerifyIdTokenResponse: LoginTicketWithoutUnusedValues = { }; describe('GoogleOAuth Service verifyIdentity', () => { - it('should return a valid Identity when Google credentials are valid', async () => { + it('should return a valid UserIdentity when Google credentials are valid', async () => { const getTokenFn = () => Promise.resolve(validGetTokenResponse); const verifyIdTokenFn = () => Promise.resolve(validVerifyIdTokenResponse); @@ -174,7 +174,7 @@ function testIt( verifyIdTokenFn: () => Promise>, mockIdGenerated: Uuid = validUserId, originHeaderValue: Url = 'http://localhost/callback' as Url -): Promise<[Identity<'google.com'>, AuthorizationForIdp<'google.com'>]> { +): Promise<[UserIdentity<'google.com'>, AuthorizationForIdp<'google.com'>]> { vi.mock('google-auth-library'); vi.mocked(OAuth2Client).mockReturnValue({ verifyIdToken: vi.fn().mockImplementation(verifyIdTokenFn), diff --git a/src/services/google/oauth.ts b/src/services/google/oauth.ts index f9c5021c7..5f4a5ca89 100644 --- a/src/services/google/oauth.ts +++ b/src/services/google/oauth.ts @@ -1,7 +1,7 @@ import type { Logger } from '@aws-lambda-powertools/logger'; import type { GoogleOAuthConfig } from '@model/Config'; import type { AuthorizationForIdp } from '@model/IdpAuthorization'; -import type { Email, Identity, IdpId } from '@notifycal/shared/types'; +import type { Email, IdpId, UserIdentity } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { throwError } from '@services/common/error-handling'; import { idGenerator } from '@services/id-generator'; @@ -19,7 +19,7 @@ export class GoogleOAuth extends BaseGoogle { public verifyIdentity( userGoogleCode: string - ): Promise<[Identity<'google.com'>, AuthorizationForIdp<'google.com'>]> { + ): Promise<[UserIdentity<'google.com'>, AuthorizationForIdp<'google.com'>]> { const getTokenrestResourceName = 'GET Token response'; return withIntegrationMetrics('google.com', getTokenrestResourceName, () => this._client.getToken(userGoogleCode) @@ -69,7 +69,7 @@ export class GoogleOAuth extends BaseGoogle { { ticket } ); } - const identity: Identity<'google.com'> = { + const userIdentity: UserIdentity<'google.com'> = { userId: idGenerator(id, 'google.com'), email: email as Email, idp: 'google.com' as TIdpName, @@ -78,7 +78,7 @@ export class GoogleOAuth extends BaseGoogle { const authorization: AuthorizationForIdp<'google.com'> = { refreshToken: tokenResponse.tokens.refresh_token as string }; - return [identity, authorization]; + return [userIdentity, authorization]; }); }); } diff --git a/src/services/jwt.test.ts b/src/services/jwt.test.ts index c4b9b135e..c4728f969 100644 --- a/src/services/jwt.test.ts +++ b/src/services/jwt.test.ts @@ -6,7 +6,7 @@ import type { EncodeRefreshJwtConfig } from '@model/Config'; import { type AccessToken, accessTokenSchema } from '@model/Jwt'; -import type { Email, Identity, IdpId, IdpName, Jwt, Uuid } from '@notifycal/shared/types'; +import type { Email, IdpId, IdpName, Jwt, UserIdentity, Uuid } from '@notifycal/shared/types'; import type { PrivateKey, PublicKey } from '@own-types/model'; import { sleep } from '@testing/utils/utils'; import { describe, expect, it } from 'vitest'; @@ -85,7 +85,7 @@ describe('Jwt builder', () => { describe('Jwts builder', () => { const userId = '09b6b481-3fa1-4ed4-b3c1-5a9467acc7ef' as Uuid; const email = 'test@notifycal.com' as Email; - const identity = { + const userIdentity = { userId: userId, email: email, idp: 'google.com' as IdpName, @@ -93,9 +93,9 @@ describe('Jwts builder', () => { }; it('should build a jwts', () => { - return expect(testit(identity, validEncodeConfig, validEncodeConfig)).resolves.toStrictEqual( - expect.any(Object) - ); + return expect( + testit(userIdentity, validEncodeConfig, validEncodeConfig) + ).resolves.toStrictEqual(expect.any(Object)); }); it('should fail to build access jwt', () => { @@ -104,7 +104,7 @@ describe('Jwts builder', () => { secretOrPrivateKey: `invalid_es256_private_key` as PrivateKey }; return expect( - testit(identity, invalidEncodeJwtConfig, validEncodeConfig) + testit(userIdentity, invalidEncodeJwtConfig, validEncodeConfig) ).rejects.toStrictEqual(new Error('Access JWT could not be generated')); }); @@ -114,16 +114,16 @@ describe('Jwts builder', () => { secretOrPrivateKey: `invalid_es256_private_key` as PrivateKey }; return expect( - testit(identity, validEncodeConfig, invalidEncodeRefreshJwtConfig) + testit(userIdentity, validEncodeConfig, invalidEncodeRefreshJwtConfig) ).rejects.toStrictEqual(new Error('Refresh JWT could not be generated')); }); function testit( - identity: Identity, + userIdentity: UserIdentity, encodeJwtConfig: EncodeAccessJwtConfig, encodeRefreshJwtConfig: EncodeRefreshJwtConfig ): Promise { - return buildJwts(identity, encodeJwtConfig, encodeRefreshJwtConfig); + return buildJwts(userIdentity, encodeJwtConfig, encodeRefreshJwtConfig); } }); diff --git a/src/services/jwt.ts b/src/services/jwt.ts index 92e1b8ca7..6deb02a8d 100644 --- a/src/services/jwt.ts +++ b/src/services/jwt.ts @@ -8,17 +8,17 @@ import { type RefreshToken } from '@model/Jwt'; import type { DecodeVonageAccessJwtConfig } from '@model/vendor/vonage/config'; -import type { Identity, IdpName, Jwt, UserId } from '@notifycal/shared/types'; +import type { IdpName, Jwt, UserId, UserIdentity } from '@notifycal/shared/types'; import jwtBuilder from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import type { z } from 'zod'; import { rejectWithErrorMessage } from './common/error-handling'; export function accessJwtPayload( - identity: Identity + userIdentity: UserIdentity ): OurAccessTokenClaims { return { - ...identity, + ...userIdentity, role: 'user', permissions: {} }; @@ -82,7 +82,7 @@ export function buildJwts< TIdpName extends IdpName, TConfig extends SignOptions & { secretOrPrivateKey: string } >( - identity: Identity, + userIdentity: UserIdentity, encodeJwtConfig: TConfig, encodeRefreshJwtConfig: TConfig ): Promise { @@ -92,10 +92,13 @@ export function buildJwts< } return Promise.all([ - buildJwt(accessJwtPayload(identity), accessTokenSchema, identity.userId, encodeJwtConfig).catch( - prependJwtType('Access') - ), - buildJwt({}, refreshTokenSchema, identity.userId, encodeRefreshJwtConfig).catch( + buildJwt( + accessJwtPayload(userIdentity), + accessTokenSchema, + userIdentity.userId, + encodeJwtConfig + ).catch(prependJwtType('Access')), + buildJwt({}, refreshTokenSchema, userIdentity.userId, encodeRefreshJwtConfig).catch( prependJwtType('Refresh') ) ]).then((jwts) => ({ diff --git a/src/services/stripe.test.ts b/src/services/stripe.test.ts index 6806f1931..f09733f4b 100644 --- a/src/services/stripe.test.ts +++ b/src/services/stripe.test.ts @@ -3,12 +3,12 @@ import { logger } from '@common/powertools'; import type { Tier, Topup } from '@model/PaymentPlans'; import type { Email, - Identity, IdpId, IdpName, LanguageCode, StripeCustomerId, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { HttpClient } from '@services/common/http-client'; @@ -31,7 +31,7 @@ const validApiKey = 'sk_test_123456789'; describe(StripeService, () => { const validUserId = 'cfaa8471-f4cc-44da-bc22-ddc4b735a847' as UserId; const validEmail = 'test@notifycal.com' as Email; - const validIdentity: Identity<'google.com'> = { + const validIdentity: UserIdentity<'google.com'> = { userId: validUserId, idp: 'google.com', idpId: '1234567890' as IdpId, @@ -89,8 +89,8 @@ describe(StripeService, () => { const result = await testCreateCustomer(validIdentity, createCustomerFn); // eslint-disable-next-line @typescript-eslint/unbound-method - expect(logger.info).toHaveBeenCalledWith('Creating customer in Stripe for identity', { - identity: validIdentity + expect(logger.info).toHaveBeenCalledWith('Creating customer in Stripe for user identity', { + userIdentity: validIdentity }); expect(result).toBe(validStripeCustomerId); expect(createCustomerFn).toHaveBeenCalledTimes(1); @@ -691,7 +691,7 @@ describe(StripeService, () => { }); async function testCreateCustomer( - identity: Identity, + userIdentity: UserIdentity, createCustomerFn: () => Promise<{ id: string }>, testClockListFn: MockInstance = vi.fn().mockRejectedValue(new Error('Testing in anger')), testClockCreateFn: MockInstance = vi.fn().mockRejectedValue(new Error('Testing in anger')) @@ -706,12 +706,12 @@ describe(StripeService, () => { setupMocks(mockStripeInstance); const stripeService = await StripeService.withConfig(validApiKey); - return stripeService.createCustomer(identity); + return stripeService.createCustomer(userIdentity); } async function testCheckoutSession( stripeCustomerId: StripeCustomerId, - identity: Identity, + userIdentity: UserIdentity, product: Tier | Topup, language: LanguageCode, successRedirectUrl: Url, @@ -732,7 +732,7 @@ describe(StripeService, () => { const stripeService = await StripeService.withConfig(validApiKey); return stripeService.createCheckoutSession( stripeCustomerId, - identity, + userIdentity, product, language, successRedirectUrl, diff --git a/src/services/stripe.ts b/src/services/stripe.ts index 117f2e4f5..22c45a1d0 100644 --- a/src/services/stripe.ts +++ b/src/services/stripe.ts @@ -3,10 +3,10 @@ import { logger } from '@common/powertools'; import type { Tier, Topup } from '@model/PaymentPlans'; import type { Email, - Identity, IdpName, LanguageCode, - StripeCustomerId + StripeCustomerId, + UserIdentity } from '@notifycal/shared/types'; import type { Url } from '@own-types/model'; import { HttpClient } from '@services/common/http-client'; @@ -62,10 +62,10 @@ export class StripeService { })); } - public createCustomer(identity: Identity): Promise { - const { userId, idp, idpId, email } = identity; - logger.info(`Creating customer in Stripe for identity`, { - identity + public createCustomer(userIdentity: UserIdentity): Promise { + const { userId, idp, idpId, email } = userIdentity; + logger.info(`Creating customer in Stripe for user identity`, { + userIdentity }); const params: Stripe.CustomerCreateParams = { email: email, @@ -85,14 +85,14 @@ export class StripeService { public createCheckoutSession( stripeCustomerId: StripeCustomerId, - identity: Identity, + userIdentity: UserIdentity, product: Tier | Topup, language: LanguageCode, successRedirectUrl: Url, cancelRedirectUrl: Url, taxId: string ): Promise { - const { userId, idp, idpId, email } = identity; + const { userId, idp, idpId, email } = userIdentity; const productConfig: Partial = match(product.type) .with('tier', () => ({ mode: 'subscription' as const })) .with('topup', () => ({ diff --git a/src/services/subscription-events.test.ts b/src/services/subscription-events.test.ts index 5f9d65dab..255d32e57 100644 --- a/src/services/subscription-events.test.ts +++ b/src/services/subscription-events.test.ts @@ -1,12 +1,12 @@ import type { CreditAdditionResult, CreditDeductionResult } from '@model/Credits'; import type { Email, - Identity, IdpId, IdpName, Percentage, TierId, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import { describe, expect, it, vi } from 'vitest'; import type { CreditsService } from './credits-service'; @@ -17,7 +17,7 @@ describe('SubscriptionService Event Publishing', () => { const validUserId = 'user-123' as UserId; const validGoodTier = 'good' as TierId; const validBetterTier = 'better' as TierId; - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: validUserId, idp: 'google.com', idpId: 'google-user-123' as IdpId, diff --git a/src/services/subscription.test.ts b/src/services/subscription.test.ts index 22da972f3..751d1a8da 100644 --- a/src/services/subscription.test.ts +++ b/src/services/subscription.test.ts @@ -2,12 +2,12 @@ import type { CreditAdditionResult, CreditDeductionResult } from '@model/Credits'; import type { Email, - Identity, IdpId, IdpName, Percentage, TierId, - UserId + UserId, + UserIdentity } from '@notifycal/shared/types'; import { describe, expect, it, vi } from 'vitest'; import type { CreditsService } from './credits-service'; @@ -19,7 +19,7 @@ describe(SubscriptionService, () => { const validGoodTier = 'good' as TierId; const validBetterTier = 'better' as TierId; const validBestTier = 'best' as TierId; - const validIdentity: Identity = { + const validIdentity: UserIdentity = { userId: validUserId, idp: 'google.com', idpId: 'google-user-123' as IdpId, @@ -115,7 +115,7 @@ describe(SubscriptionService, () => { function testItCreate( resetFn: () => Promise, - identity: Identity = validIdentity, + userIdentity: UserIdentity = validIdentity, tier: TierId = validGoodTier, map = validTierToCreditsMap ): Promise { @@ -124,7 +124,7 @@ describe(SubscriptionService, () => { map, mockSnsService ); - return service.create(identity, tier); + return service.create(userIdentity, tier); } }); @@ -177,7 +177,7 @@ describe(SubscriptionService, () => { function testItRenew( resetFn: () => Promise, - identity: Identity = validIdentity, + userIdentity: UserIdentity = validIdentity, tier: TierId = validGoodTier, map = validTierToCreditsMap ): Promise { @@ -186,7 +186,7 @@ describe(SubscriptionService, () => { map, mockSnsService ); - return service.renew(identity, tier); + return service.renew(userIdentity, tier); } }); @@ -285,7 +285,7 @@ describe(SubscriptionService, () => { function testItUpgrade( addFn: () => Promise, - identity: Identity, + userIdentity: UserIdentity, prev: TierId, curr: TierId, remainingPercentage: Percentage @@ -295,7 +295,7 @@ describe(SubscriptionService, () => { validTierToCreditsMap, mockSnsService ); - return service.upgrade(identity, prev, curr, remainingPercentage); + return service.upgrade(userIdentity, prev, curr, remainingPercentage); } }); diff --git a/src/services/subscription.ts b/src/services/subscription.ts index f25f902ed..534d62e5e 100644 --- a/src/services/subscription.ts +++ b/src/services/subscription.ts @@ -1,6 +1,6 @@ import { logger } from '@common/powertools'; import * as SubscriptionEvents from '@model/app-events/subscription-events'; -import type { Identity, IdpName, Percentage, TierId } from '@notifycal/shared/types'; +import type { IdpName, Percentage, TierId, UserIdentity } from '@notifycal/shared/types'; import type { CreditsService } from './credits-service'; import type { SnsService } from './sns'; @@ -28,34 +28,42 @@ export class SubscriptionService { private readonly snsService: SnsService ) {} - public create(identity: Identity, tier: TierId): Promise { + public create(userIdentity: UserIdentity, tier: TierId): Promise { const credits = this.tierToCreditsMap[tier]; - const operation = this.creditsService.resetSubscriptionCredits(identity.userId, credits, tier); + const operation = this.creditsService.resetSubscriptionCredits( + userIdentity.userId, + credits, + tier + ); return handleServiceOperation( operation, - (result) => SubscriptionEvents.subscriptionCreatedEvent(identity, tier, result), + (result) => SubscriptionEvents.subscriptionCreatedEvent(userIdentity, tier, result), (result, error) => - SubscriptionEvents.subscriptionCreationFailedEvent(identity, tier, result, error), + SubscriptionEvents.subscriptionCreationFailedEvent(userIdentity, tier, result, error), this.snsService ); } - public renew(identity: Identity, tier: TierId): Promise { + public renew(userIdentity: UserIdentity, tier: TierId): Promise { const credits = this.tierToCreditsMap[tier]; - const operation = this.creditsService.resetSubscriptionCredits(identity.userId, credits, tier); + const operation = this.creditsService.resetSubscriptionCredits( + userIdentity.userId, + credits, + tier + ); return handleServiceOperation( operation, - (result) => SubscriptionEvents.subscriptionRenewedEvent(identity, tier, result), + (result) => SubscriptionEvents.subscriptionRenewedEvent(userIdentity, tier, result), (result, error) => - SubscriptionEvents.subscriptionRenewalFailedEvent(identity, tier, result, error), + SubscriptionEvents.subscriptionRenewalFailedEvent(userIdentity, tier, result, error), this.snsService ); } public upgrade( - identity: Identity, + userIdentity: UserIdentity, previousTier: TierId, currentTier: TierId, remainingPercentage: Percentage @@ -81,7 +89,7 @@ export class SubscriptionService { return this.snsService .safePublish( SubscriptionEvents.subscriptionUpgradeFailedEvent( - identity, + userIdentity, previousTier, currentTier, remainingPercentage, @@ -102,7 +110,7 @@ export class SubscriptionService { return this.snsService .safePublish( SubscriptionEvents.subscriptionUpgradeFailedEvent( - identity, + userIdentity, previousTier, currentTier, remainingPercentage, @@ -114,7 +122,7 @@ export class SubscriptionService { .then(() => result); } - const operation = this.creditsService.addCredits(identity.userId, creditsToAdd, { + const operation = this.creditsService.addCredits(userIdentity.userId, creditsToAdd, { type: 'subscription', id: currentTier }); @@ -123,7 +131,7 @@ export class SubscriptionService { operation, (result) => SubscriptionEvents.subscriptionUpgradedEvent( - identity, + userIdentity, previousTier, currentTier, remainingPercentage, @@ -132,7 +140,7 @@ export class SubscriptionService { ), (result, error) => SubscriptionEvents.subscriptionUpgradeFailedEvent( - identity, + userIdentity, previousTier, currentTier, remainingPercentage, @@ -144,27 +152,27 @@ export class SubscriptionService { ); } - public scheduleDowngrade(identity: Identity): Promise { + public scheduleDowngrade(userIdentity: UserIdentity): Promise { logger.info('Downgrade scheduled. Nothing to do. Credits will be reset on next cycle', { - userId: identity.userId + userId: userIdentity.userId }); return this.snsService.safePublish( - SubscriptionEvents.subscriptionDowngradeScheduledEvent(identity) + SubscriptionEvents.subscriptionDowngradeScheduledEvent(userIdentity) ); } public cancel( - identity: Identity, + userIdentity: UserIdentity, reason: 'unpaid' | 'cancelled' ): Promise { - const operation = this.creditsService.clearSubscriptionCredits(identity.userId, reason); + const operation = this.creditsService.clearSubscriptionCredits(userIdentity.userId, reason); return handleServiceOperation( operation, - (result) => SubscriptionEvents.subscriptionCancelledEvent(identity, reason, result), + (result) => SubscriptionEvents.subscriptionCancelledEvent(userIdentity, reason, result), (result, error) => - SubscriptionEvents.subscriptionCancellationFailedEvent(identity, reason, result, error), + SubscriptionEvents.subscriptionCancellationFailedEvent(userIdentity, reason, result, error), this.snsService ); } diff --git a/src/services/topup.test.ts b/src/services/topup.test.ts index e31cee347..a6a92586e 100644 --- a/src/services/topup.test.ts +++ b/src/services/topup.test.ts @@ -1,12 +1,12 @@ import type { CreditAdditionResult } from '@model/Credits'; -import type { Email, Identity, IdpId, TierId, TopupId, UserId } from '@notifycal/shared/types'; +import type { Email, IdpId, TierId, TopupId, UserId, UserIdentity } from '@notifycal/shared/types'; import { describe, expect, it, vi } from 'vitest'; import type { CreditsService } from './credits-service'; import type { SnsService } from './sns'; import { TopupService } from './topup'; describe(TopupService, () => { - const validIdentity: Identity<'google.com'> = { + const validIdentity: UserIdentity<'google.com'> = { idp: 'google.com', idpId: 'idp-123' as IdpId, userId: 'user-123' as UserId, @@ -187,7 +187,7 @@ describe(TopupService, () => { }); function testIt( - identity: Identity<'google.com'>, + userIdentity: UserIdentity<'google.com'>, topup: TopupId, quantity: number, addCreditsFn: ( @@ -206,6 +206,6 @@ describe(TopupService, () => { } as unknown as SnsService; const topupService = new TopupService(creditsServiceMock, topupToCreditsMap, snsServiceMock); - return topupService.add(identity, topup, quantity); + return topupService.add(userIdentity, topup, quantity); } }); diff --git a/src/services/topup.ts b/src/services/topup.ts index 7acfbdce2..aba1e5a0f 100644 --- a/src/services/topup.ts +++ b/src/services/topup.ts @@ -1,7 +1,7 @@ import { topupFailedEvent } from '@model/app-events/TopupFailedEvent'; import { topupSucceededEvent } from '@model/app-events/TopupSucceededEvent'; import type { CreditAdditionResult } from '@model/Credits'; -import type { Identity, IdpName, TopupId } from '@notifycal/shared/types'; +import type { IdpName, TopupId, UserIdentity } from '@notifycal/shared/types'; import { handleServiceOperation } from './common/error-handling'; import type { CreditsService } from './credits-service'; import type { SnsService } from './sns'; @@ -13,7 +13,7 @@ export class TopupService { private readonly snsService: SnsService ) {} public add( - identity: Identity, + userIdentity: UserIdentity, topup: TopupId, quantity: number ): Promise { @@ -22,19 +22,19 @@ export class TopupService { `Error while adding a topup. Quantity cannot be smaller than 1. Quantity: ${quantity}` ); return this.snsService - .safePublish(topupFailedEvent(identity, topup, quantity, 0, undefined, error)) + .safePublish(topupFailedEvent(userIdentity, topup, quantity, 0, undefined, error)) .then(() => Promise.reject(error)); } const credits = this.topupToCreditsMap[topup] * quantity; - const operation = this.creditsService.addCredits(identity.userId, credits, { + const operation = this.creditsService.addCredits(userIdentity.userId, credits, { type: 'topup', id: 'single' }); return handleServiceOperation( operation, - (result) => topupSucceededEvent(identity, topup, quantity, credits, result), - (result, error) => topupFailedEvent(identity, topup, quantity, credits, result, error), + (result) => topupSucceededEvent(userIdentity, topup, quantity, credits, result), + (result, error) => topupFailedEvent(userIdentity, topup, quantity, credits, result, error), this.snsService ); } diff --git a/src/testing/utils/jwt.ts b/src/testing/utils/jwt.ts index 15567d765..67766f926 100644 --- a/src/testing/utils/jwt.ts +++ b/src/testing/utils/jwt.ts @@ -5,7 +5,7 @@ import type { EncodeAccessJwtConfig } from '@model/Config'; import { accessTokenSchema, type OurAccessTokenClaims } from '@model/Jwt'; -import type { Email, Identity, IdpId, Jwt, UnixTimestamp, Uuid } from '@notifycal/shared/types'; +import type { Email, IdpId, Jwt, UnixTimestamp, UserIdentity, Uuid } from '@notifycal/shared/types'; import type { PrivateKey, PublicKey } from '@own-types/model'; import { buildJwt, type EncodedAndDecodedJwts } from '@services/jwt'; import dotenv from 'dotenv'; @@ -30,14 +30,14 @@ const loadDevConfig: () => Record = (() => { const userId = '09b6b481-3fa1-4ed4-b3c1-5a9467acc7ef' as Uuid; const email = 'test@notifycal.com' as Email; -const identity: Identity<'google.com'> = { +const userIdentity: UserIdentity<'google.com'> = { userId: userId, email: email, idp: 'google.com', idpId: '46345747457457' as IdpId }; export const getDefaultAccessTokenPayload: () => OurAccessTokenClaims = () => ({ - ...identity, + ...userIdentity, role: 'user', permissions: {} }); @@ -90,14 +90,14 @@ export const validJwts: EncodedAndDecodedJwts = { typ: 'JWT' }, payload: { - ...identity, + ...userIdentity, role: 'user', permissions: {}, iat: 1735311407 as UnixTimestamp, exp: 1735512345 as UnixTimestamp, aud: 'local.notifycal.com', iss: 'local.notifycal.com', - sub: identity.userId, + sub: userIdentity.userId, jti: '9999999-d54b-4f70-90e1-59c02d0e7a02' as Uuid }, signature: 'some_signature' @@ -115,7 +115,7 @@ export const validJwts: EncodedAndDecodedJwts = { exp: 1735599999 as UnixTimestamp, aud: 'local.notifycal.com', iss: 'local.notifycal.com', - sub: identity.userId, + sub: userIdentity.userId, jti: '8888888-d54b-4f70-90e1-59c02d0e7a02' as Uuid }, signature: 'some_signature'