UNPKG

@sourceloop/ctrl-plane-tenant-management-service

Version:

Tenant Management microservice for SaaS control plane

165 lines (158 loc) 5.74 kB
import {inject, service} from '@loopback/core'; import {Transaction, repository} from '@loopback/repository'; import {HttpErrors} from '@loopback/rest'; import {ILogger, LOGGER} from '@sourceloop/core'; import {webhookHandler} from '../../decorators'; import {NotificationType, TenantStatus} from '../../enums'; import {WebhookType} from '../../enums/webhook-types.enum'; import {PermissionKey} from '../../permissions'; import {ResourceRepository, TenantRepository} from '../../repositories'; import { IWebhookHandler, ResourceProvisionedWebhookPayload, ResourceTypes, WebhookStatus, } from '../../types'; import {CryptoHelperService} from '../crypto-helper.service'; import {NotificationService} from '../notifications'; import {IPostWebhookHandlerService} from '../../types/i-post-webhook-handler-service.interface'; import {PostWebhookHandlerServiceKey} from '../../keys'; import {SaasTenantRepository} from '../../repositories/saas-tenant.repository'; /** * Handler for provisioning webhooks. * This class implements the IWebhookHandler interface and defines the type of webhook it handles * as `WebhookType.RESOURCES_PROVISIONED`. It provides functionality to handle a webhook payload * for resource provisioning, updating the status of a tenant and creating resources associated with the tenant. */ @webhookHandler() export class ProvisioningWebhookHandler implements IWebhookHandler { type: WebhookType = WebhookType.RESOURCES_PROVISIONED; /** * Constructs a new instance of the ProvisioningWebhookHandler. * @param {ResourceRepository} resourceRepository - Repository for managing resources. * @param {TenantRepository} tenantRepository - Repository for managing tenants. * @param {NotificationService} notificationService - Service for sending notifications. * @param {CryptoHelperService} cryptoHelperService - Service for cryptographic operations. * @param {ILogger} logger - Logger service for logging messages. */ constructor( @repository(ResourceRepository) public resourceRepository: ResourceRepository, @repository(SaasTenantRepository) public tenantRepository: TenantRepository, @inject('services.NotificationService') private notificationService: NotificationService, @inject(PostWebhookHandlerServiceKey) private postWebhookHandlerService: IPostWebhookHandlerService<ResourceProvisionedWebhookPayload>, @service(CryptoHelperService) private cryptoHelperService: CryptoHelperService, @inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger, ) {} /** * This function handles a webhook payload for resource provisioning, updating the status of a tenant * and creating resources associated with the tenant. * @param {ResourceProvisionedWebhookPayload} payload - The payload parameter is of type * ResourceProvisionedWebhookPayload. */ async handle(payload: ResourceProvisionedWebhookPayload): Promise<void> { const transaction = await this.resourceRepository.beginTransaction(); try { const existing = await this.tenantRepository.findOne( { where: { id: payload.initiatorId, status: TenantStatus.PROVISIONING, }, }, { transaction, }, ); if (!existing) { this.logger.error('Tenant not found or not in provisioning state'); throw new HttpErrors.Unauthorized(); } if (payload.data.status === WebhookStatus.SUCCESS) { await this.tenantRepository.updateById( payload.initiatorId, { status: TenantStatus.ACTIVE, }, {transaction}, ); if (payload.data.resources.length > 0) { await this.resourceRepository.create({ tenantId: payload.initiatorId, externalIdentifier: payload.data.resources[0]?.metadata?.bucket, type: ResourceTypes.BUCKET, metadata: payload.data.resources[0].metadata, }); } await this._sendEmail(transaction, payload); } else { await this.tenantRepository.updateById( payload.initiatorId, { status: TenantStatus.PROVISIONFAILED, }, {transaction}, ); } await this.postWebhookHandlerService.postWebhookHandler(payload); await transaction.commit(); } catch (e) { await transaction.rollback(); this.logger.error(e); throw e; } } private async _sendEmail( transaction: Transaction, payload: ResourceProvisionedWebhookPayload, ) { const tenant = await this.tenantRepository.findById( payload.initiatorId, { include: [ { relation: 'contacts', scope: { where: { isPrimary: true, }, fields: ['email', 'firstName', 'lastName', 'tenantId', 'deleted'], order: ['createdOn DESC'], }, }, ], }, { transaction, }, ); const email = tenant.contacts?.[0]?.email; const name = tenant.contacts?.[0]?.firstName; if (!email) { this.logger.error(`No email found to notify tenant: ${tenant.id}`); } else { const tempToken = this.cryptoHelperService.generateTempTokenForTenant( tenant, [ PermissionKey.CreateNotification, PermissionKey.ViewNotificationTemplate, ], ); await this.notificationService.send( email, NotificationType.WelcomeTenant, { name: tenant.name, user: name, link: payload.data.appPlaneUrl, }, tempToken, ); } } }