From 6ad2f48eca37747fa0979c9e7f76f0d4f9117828 Mon Sep 17 00:00:00 2001 From: Tyagi-Sunny Date: Wed, 5 Jun 2024 13:40:35 +0530 Subject: [PATCH] feat(tenant-management): provision to add tenant offboarding provision to add tenant offboarding gh-6 --- .../src/__tests__/acceptance/mock-data.ts | 12 + .../tenant.controller.acceptance.ts | 7 +- .../webhook.controller.acceptance.ts | 144 +- .../src/component.ts | 4 + .../src/controllers/tenant.controller.ts | 43 +- .../src/enums/off-board.enum.ts | 4 + .../src/enums/tenant-status.enum.ts | 2 + .../src/enums/webhook-types.enum.ts | 1 + .../webhook-verifier.interceptor.ts | 12 +- .../tenant-management-service/src/keys.ts | 3 + .../src/models/dtos/tenant-tier-dto.model.ts | 15 + .../src/openapi.json | 1823 +++++++++-------- .../src/permissions.ts | 2 +- .../src/services/index.ts | 1 + .../src/services/off-board.service.ts | 60 + .../src/services/webhook/index.ts | 1 + .../webhook/offboarding-webhook.handler.ts | 152 ++ .../src/types/webhook-payload.type.ts | 7 +- .../src/webhook.component.ts | 18 +- 19 files changed, 1491 insertions(+), 820 deletions(-) create mode 100644 services/tenant-management-service/src/enums/off-board.enum.ts create mode 100644 services/tenant-management-service/src/models/dtos/tenant-tier-dto.model.ts create mode 100644 services/tenant-management-service/src/services/off-board.service.ts create mode 100644 services/tenant-management-service/src/services/webhook/offboarding-webhook.handler.ts diff --git a/services/tenant-management-service/src/__tests__/acceptance/mock-data.ts b/services/tenant-management-service/src/__tests__/acceptance/mock-data.ts index 54e8266..027433b 100644 --- a/services/tenant-management-service/src/__tests__/acceptance/mock-data.ts +++ b/services/tenant-management-service/src/__tests__/acceptance/mock-data.ts @@ -152,6 +152,18 @@ export const mockWebhookPayload: WebhookPayload = { }), ], appPlaneUrl: 'redirectUrl', + tier: PlanTier.SILO, + }, +}; + +export const mockOffboardingWebhookPayload: WebhookPayload = { + initiatorId: 'user-id-1', + type: WebhookType.TENANT_OFFBOARDING, + data: { + status: WebhookStatus.SUCCESS, + resources: [], + appPlaneUrl: '', + tier: PlanTier.SILO, }, }; diff --git a/services/tenant-management-service/src/__tests__/acceptance/tenant.controller.acceptance.ts b/services/tenant-management-service/src/__tests__/acceptance/tenant.controller.acceptance.ts index 93227b7..ac92ef7 100644 --- a/services/tenant-management-service/src/__tests__/acceptance/tenant.controller.acceptance.ts +++ b/services/tenant-management-service/src/__tests__/acceptance/tenant.controller.acceptance.ts @@ -22,7 +22,8 @@ import {BindingScope} from '@loopback/context'; import {AWS_CODEBUILD_CLIENT} from '../../services'; import {CodeBuildClient, StartBuildCommand} from '@aws-sdk/client-codebuild'; import {PlanTier} from '../../enums'; -import {PIPELINES} from '../../keys'; +import {OFFBOARDING_PIPELINES, PIPELINES} from '../../keys'; +import {OffBoard} from '../../enums/off-board.enum'; describe('TenantController', () => { let app: TenantMgmtServiceApplication; @@ -50,6 +51,10 @@ describe('TenantController', () => { [PlanTier.POOLED]: 'free-pipeline', [PlanTier.SILO]: '', }); + app.bind(OFFBOARDING_PIPELINES).to({ + [OffBoard.POOLED]: 'free-offboard-pipeline', + [OffBoard.SILO]: '', + }); secretRepo = await getRepo(app, 'repositories.WebhookSecretRepository'); }); diff --git a/services/tenant-management-service/src/__tests__/acceptance/webhook.controller.acceptance.ts b/services/tenant-management-service/src/__tests__/acceptance/webhook.controller.acceptance.ts index e651d56..48ed8a2 100644 --- a/services/tenant-management-service/src/__tests__/acceptance/webhook.controller.acceptance.ts +++ b/services/tenant-management-service/src/__tests__/acceptance/webhook.controller.acceptance.ts @@ -3,8 +3,8 @@ import {Client, expect, sinon} from '@loopback/testlab'; import {ILogger, LOGGER, STATUS_CODE} from '@sourceloop/core'; import {createHmac, randomBytes} from 'crypto'; import {TenantMgmtServiceApplication} from '../../application'; -import {TenantStatus} from '../../enums'; -import {WEBHOOK_CONFIG} from '../../keys'; +import {TenantStatus, WebhookType} from '../../enums'; +import {OFFBOARDING_PIPELINES, WEBHOOK_CONFIG} from '../../keys'; import { ContactRepository, ResourceRepository, @@ -12,8 +12,14 @@ import { WebhookSecretRepository, } from '../../repositories'; import {ResourceData, WebhookConfig, WebhookPayload} from '../../types'; -import {mockContact, mockWebhookPayload, testTemplates} from './mock-data'; +import { + mockContact, + mockOffboardingWebhookPayload, + mockWebhookPayload, + testTemplates, +} from './mock-data'; import {getRepo, setupApplication} from './test-helper'; +import {OffBoard} from '../../enums/off-board.enum'; describe('WebhookController', () => { let app: TenantMgmtServiceApplication; @@ -24,6 +30,7 @@ describe('WebhookController', () => { let tenantRepo: TenantRepository; let contactRepo: ContactRepository; let webhookPayload: WebhookPayload; + let offboardWebhookPayload: WebhookPayload; const nonExistantTenant = 'non-existant-tenant'; const notifStub = sinon.stub(); @@ -49,6 +56,10 @@ describe('WebhookController', () => { createNotification: notifStub, getTemplateByName: (name: string) => testTemplates[name], }); + app.bind(OFFBOARDING_PIPELINES).to({ + [OffBoard.POOLED]: 'free-offboard-pipeline', + [OffBoard.SILO]: '', + }); }); after(async () => { @@ -62,10 +73,14 @@ describe('WebhookController', () => { notifStub.resolves(); await resourceRepo.deleteAllHard(); await tenantRepo.deleteAllHard(); - const tenant = await seedTenant(); + const {newTenant, newTenant2} = await seedTenant(); webhookPayload = { ...mockWebhookPayload, - initiatorId: tenant.id, + initiatorId: newTenant.id, + }; + offboardWebhookPayload = { + ...mockOffboardingWebhookPayload, + initiatorId: newTenant2.id, }; }); @@ -158,6 +173,95 @@ describe('WebhookController', () => { }); }); + describe('For Offboarding', () => { + it('should successfully call the webhook handler for a valid payload', async () => { + const headers = await buildHeaders(offboardWebhookPayload); + await client + .post('/webhook') + .set(webhookConfig.signatureHeaderName, headers.signature) + .set(webhookConfig.timestampHeaderName, headers.timestamp) + .send(offboardWebhookPayload) + .expect(STATUS_CODE.NO_CONTENT); + + const tenant = await tenantRepo.findById( + offboardWebhookPayload.initiatorId, + ); + expect(tenant.status).to.equal(TenantStatus.INACTIVE); + + // should send an email to primary contact as well for successful provisioning + const calls = notifStub.getCalls(); + expect(calls).to.have.length(1); + // extract and validate data from the email + const emailData = calls[0].args[2]; + const receiver = calls[0].args[0]; + expect(emailData.link).to.be.String(); + expect(emailData.name).to.equal(tenant.name); + expect(emailData.user).to.equal(mockContact.firstName); + expect(receiver).to.equal(mockContact.email); + + // verify the resource was deleted + const resources = await resourceRepo.find({ + where: { + tenantId: offboardWebhookPayload.initiatorId, + }, + }); + expect(resources).to.have.length(0); + }); + + it('should successfully call the provisioning handler but skips mail for a valid payload but contact missing', async () => { + const headers = await buildHeaders(offboardWebhookPayload); + // delete contact to avoid sending email + await contactRepo.deleteAllHard(); + + await client + .post('/webhook') + .set(webhookConfig.signatureHeaderName, headers.signature) + .set(webhookConfig.timestampHeaderName, headers.timestamp) + .send(offboardWebhookPayload) + .expect(STATUS_CODE.NO_CONTENT); + + const tenant = await tenantRepo.findById( + offboardWebhookPayload.initiatorId, + ); + expect(tenant.status).to.equal(TenantStatus.INACTIVE); + + // should throw an error if contact not found for the tenant + const calls = notifStub.getCalls(); + expect(calls).to.have.length(0); + // extract and validate data from the email + sinon.assert.calledWith( + loggerSpy.error, + `No email found to notify tenant: ${tenant.id}`, + ); + }); + + it('should return 401 if the initiator id is for tenant that does not exist', async () => { + const newPayload = { + ...offboardWebhookPayload, + initiatorId: nonExistantTenant, + }; + const headers = await buildHeaders(newPayload); + await client + .post('/webhook') + .set(webhookConfig.signatureHeaderName, headers.signature) + .set(webhookConfig.timestampHeaderName, headers.timestamp) + .send(newPayload) + .expect(STATUS_CODE.UNAUTHORISED); + + const resources = await resourceRepo.find({ + where: { + tenantId: newPayload.initiatorId, + }, + }); + + expect(resources).to.have.length(0); + const tenant = await tenantRepo.findById( + offboardWebhookPayload.initiatorId, + ); + expect(tenant.status).to.equal(TenantStatus.OFFBOARDING); + }); + }); + describe('For Provisioning', () => { it('should successfully call the provisioning handler for a valid payload', async () => { const headers = await buildHeaders(webhookPayload); @@ -276,6 +380,12 @@ describe('WebhookController', () => { key: 'test-tenant-key', domains: ['test.com'], }); + const newTenant2 = await tenantRepo.create({ + name: 'test-tenant-offboarding', + status: TenantStatus.OFFBOARDING, + key: 'test-tenant-key-offboarding', + domains: ['test-offboard.com'], + }); await contactRepo.createAll([ { ...mockContact, @@ -283,7 +393,14 @@ describe('WebhookController', () => { tenantId: newTenant.id, }, ]); - return newTenant; + await contactRepo.createAll([ + { + ...mockContact, + isPrimary: true, + tenantId: newTenant2.id, + }, + ]); + return {newTenant, newTenant2}; } async function buildHeaders(payload: WebhookPayload, tmp?: number) { @@ -296,10 +413,17 @@ describe('WebhookController', () => { const secretRepo = app.getSync( 'repositories.WebhookSecretRepository', ); - await secretRepo.set(context, { - secret, - context: payload.initiatorId, - }); + if (payload.type === WebhookType.TENANT_OFFBOARDING) { + await secretRepo.set(`${context}:offboarding`, { + secret, + context: payload.initiatorId, + }); + } else { + await secretRepo.set(context, { + secret, + context: payload.initiatorId, + }); + } return { timestamp, signature, diff --git a/services/tenant-management-service/src/component.ts b/services/tenant-management-service/src/component.ts index 150c134..b795679 100644 --- a/services/tenant-management-service/src/component.ts +++ b/services/tenant-management-service/src/component.ts @@ -77,9 +77,11 @@ import { InvoicePDFGenerator, LeadAuthenticator, NotificationService, + OffBoardService, OnboardingService, ProvisioningService, } from './services'; +import {OffBoardingWebhookHandler} from './services/webhook'; export class TenantManagementServiceComponent implements Component { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) @@ -153,6 +155,8 @@ export class TenantManagementServiceComponent implements Component { Binding.bind(AWS_CODEBUILD_CLIENT).toProvider(CodebuildClientProvider), createServiceBinding(ProvisioningService), createServiceBinding(OnboardingService), + createServiceBinding(OffBoardService), + createServiceBinding(OffBoardingWebhookHandler), createServiceBinding(LeadAuthenticator), createServiceBinding(CryptoHelperService), Binding.bind('services.NotificationService').toClass(NotificationService), diff --git a/services/tenant-management-service/src/controllers/tenant.controller.ts b/services/tenant-management-service/src/controllers/tenant.controller.ts index cdd2f6d..2773d43 100644 --- a/services/tenant-management-service/src/controllers/tenant.controller.ts +++ b/services/tenant-management-service/src/controllers/tenant.controller.ts @@ -26,8 +26,14 @@ import {TenantRepository} from '../repositories/tenant.repository'; import {SubscriptionDTO, Tenant, TenantOnboardDTO} from '../models'; import {PermissionKey} from '../permissions'; import {service} from '@loopback/core'; -import {OnboardingService, ProvisioningService} from '../services'; +import { + OffBoardService, + OnboardingService, + ProvisioningService, +} from '../services'; import {IProvisioningService} from '../types'; +import {TenantTierDTO} from '../models/dtos/tenant-tier-dto.model'; +import {TenantStatus} from '../enums'; const basePath = '/tenants'; @@ -39,6 +45,8 @@ export class TenantController { private readonly onboarding: OnboardingService, @service(ProvisioningService) private readonly provisioningService: IProvisioningService, + @service(OffBoardService) + private readonly offBoardingService: OffBoardService, ) {} @authorize({ @@ -107,6 +115,39 @@ export class TenantController { return this.provisioningService.provisionTenant(existing, dto); } + @authorize({ + permissions: [PermissionKey.OffBoardTenant], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(`${basePath}/{id}/off-board`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'offboarding success', + }, + }, + }) + async offboard( + @requestBody({ + content: { + [CONTENT_TYPE.JSON]: { + schema: getModelSchemaRef(TenantTierDTO, { + title: 'TenantTierDTO', + }), + }, + }, + }) + dto: TenantTierDTO, + @param.path.string('id') id: string, + ): Promise { + await this.tenantRepository.updateById(id, { + status: TenantStatus.OFFBOARDING, + }); + return this.offBoardingService.offBoardTenant(id, dto); + } + @authorize({ permissions: [PermissionKey.ViewTenant], }) diff --git a/services/tenant-management-service/src/enums/off-board.enum.ts b/services/tenant-management-service/src/enums/off-board.enum.ts new file mode 100644 index 0000000..56efc6e --- /dev/null +++ b/services/tenant-management-service/src/enums/off-board.enum.ts @@ -0,0 +1,4 @@ +export enum OffBoard { + POOLED, + SILO, +} diff --git a/services/tenant-management-service/src/enums/tenant-status.enum.ts b/services/tenant-management-service/src/enums/tenant-status.enum.ts index 77e1caf..d06a0a3 100644 --- a/services/tenant-management-service/src/enums/tenant-status.enum.ts +++ b/services/tenant-management-service/src/enums/tenant-status.enum.ts @@ -5,4 +5,6 @@ export enum TenantStatus { PROVISIONFAILED, DEPROVISIONING, INACTIVE, + OFFBOARDING, + OFFBOARDING_RETRY, } diff --git a/services/tenant-management-service/src/enums/webhook-types.enum.ts b/services/tenant-management-service/src/enums/webhook-types.enum.ts index 6509703..2d7dd27 100644 --- a/services/tenant-management-service/src/enums/webhook-types.enum.ts +++ b/services/tenant-management-service/src/enums/webhook-types.enum.ts @@ -1,3 +1,4 @@ export enum WebhookType { RESOURCES_PROVISIONED, + TENANT_OFFBOARDING, } diff --git a/services/tenant-management-service/src/interceptors/webhook-verifier.interceptor.ts b/services/tenant-management-service/src/interceptors/webhook-verifier.interceptor.ts index e4dd56c..d022e18 100644 --- a/services/tenant-management-service/src/interceptors/webhook-verifier.interceptor.ts +++ b/services/tenant-management-service/src/interceptors/webhook-verifier.interceptor.ts @@ -7,7 +7,7 @@ import { inject, service, } from '@loopback/core'; -import {WebhookConfig, WebhookPayload} from '../types'; +import {SecretInfo, WebhookConfig, WebhookPayload} from '../types'; import {HttpErrors, RequestContext} from '@loopback/rest'; import {SYSTEM_USER, WEBHOOK_CONFIG} from '../keys'; import {CryptoHelperService} from '../services'; @@ -16,6 +16,7 @@ import {WebhookSecretRepository} from '../repositories'; import {ILogger, LOGGER} from '@sourceloop/core'; import {timingSafeEqual} from 'crypto'; import {AuthenticationBindings, IAuthUser} from 'loopback4-authentication'; +import {WebhookType} from '../enums'; export class WebhookVerifierProvider implements Provider { constructor( @@ -56,8 +57,15 @@ export class WebhookVerifierProvider implements Provider { throw new HttpErrors.Unauthorized(); } const initiatorId = value.initiatorId; + let secretInfo: SecretInfo; + if (value.type === WebhookType.TENANT_OFFBOARDING) { + secretInfo = await this.webhookSecretRepo.get( + `${initiatorId}:offboarding`, + ); + } else { + secretInfo = await this.webhookSecretRepo.get(initiatorId); + } - const secretInfo = await this.webhookSecretRepo.get(initiatorId); if (!secretInfo) { this.logger.error('No secret found for this initiator'); throw new HttpErrors.Unauthorized(); diff --git a/services/tenant-management-service/src/keys.ts b/services/tenant-management-service/src/keys.ts index 26213a6..79c5dd4 100644 --- a/services/tenant-management-service/src/keys.ts +++ b/services/tenant-management-service/src/keys.ts @@ -33,6 +33,9 @@ export const LEAD_TOKEN_VERIFIER = BindingKey.create< export const PIPELINES = BindingKey.create>( 'sf.tenant.pipelines', ); +export const OFFBOARDING_PIPELINES = BindingKey.create>( + 'sf.tenant.offboarding.pipelines', +); /** * Binding key for the system user. diff --git a/services/tenant-management-service/src/models/dtos/tenant-tier-dto.model.ts b/services/tenant-management-service/src/models/dtos/tenant-tier-dto.model.ts new file mode 100644 index 0000000..c3f3809 --- /dev/null +++ b/services/tenant-management-service/src/models/dtos/tenant-tier-dto.model.ts @@ -0,0 +1,15 @@ +import {Model, model, property} from '@loopback/repository'; +import {PlanTier} from '../../enums'; + +@model() +export class TenantTierDTO extends Model { + @property({ + type: 'string', + required: true, + }) + tier: PlanTier; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/tenant-management-service/src/openapi.json b/services/tenant-management-service/src/openapi.json index efa5cbb..3531f8c 100644 --- a/services/tenant-management-service/src/openapi.json +++ b/services/tenant-management-service/src/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "title": "tenant-mgmt-service", + "title": "Audit Service", "version": "1.0.0", "description": "tenant-mgmt-service", "contact": { @@ -391,6 +391,44 @@ "operationId": "InvoiceController.count" } }, + "/invoices/download": { + "get": { + "x-controller-name": "InvoiceController", + "x-operation-name": "downloadInvoice", + "tags": [ + "InvoiceController" + ], + "security": [ + { + "HTTPBearer": [] + } + ], + "responses": { + "200": { + "description": "Invoice model instance", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice" + } + } + } + } + }, + "description": "\n\n| Permissions |\n| ------- |\n| 10212 |\n", + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "required": true + } + ], + "operationId": "InvoiceController.downloadInvoice" + } + }, "/invoices/{id}": { "put": { "x-controller-name": "InvoiceController", @@ -1168,6 +1206,46 @@ "operationId": "TenantController.count" } }, + "/tenants/{id}/off-board": { + "post": { + "x-controller-name": "TenantController", + "x-operation-name": "offboard", + "tags": [ + "TenantController" + ], + "security": [ + { + "HTTPBearer": [] + } + ], + "responses": { + "204": { + "description": "offboarding success" + } + }, + "description": "\n\n| Permissions |\n| ------- |\n| 10217 |\n", + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantTierDTO" + } + } + } + }, + "operationId": "TenantController.offboard" + } + }, "/tenants/{id}/provision": { "post": { "x-controller-name": "TenantController", @@ -1200,7 +1278,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProvisionDTO" + "$ref": "#/components/schemas/SubscriptionDTO" } } } @@ -1564,33 +1642,10 @@ } }, "schemas": { - "WebhookDTO": { - "title": "WebhookDTO", - "type": "object", - "description": "(tsType: WebhookDTO, schemaOptions: { title: 'WebhookDTO' })", - "properties": { - "initiatorId": { - "type": "string" - }, - "data": { - "type": "object" - }, - "type": { - "type": "number" - } - }, - "required": [ - "initiatorId", - "data", - "type" - ], - "additionalProperties": false, - "x-typescript-type": "WebhookDTO" - }, - "Tenant": { - "title": "Tenant", + "Contact": { + "title": "Contact", "type": "object", - "description": "main model of the service that represents a tenant in the system, either pooled or siloed", + "description": "contacts belonging to a tenant", "properties": { "deleted": { "type": "boolean" @@ -1621,288 +1676,107 @@ "id": { "type": "string" }, - "name": { + "firstName": { "type": "string", - "description": "name of the tenant" - }, - "status": { - "type": "number", - "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", - "enum": [ - 0, - 1, - 2, - 3, - 4, - 5 - ] + "description": "first name of the lead" }, - "key": { + "lastName": { "type": "string", - "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 + "description": "last name of the lead" }, - "spocUserId": { + "email": { "type": "string", - "description": "user id of the admin user who acts as a spoc for this tenant." + "description": "email id of the contact" }, - "domains": { - "type": "array", - "items": { - "type": "string", - "description": "array of domains that are allowed for this tenant" - } + "isPrimary": { + "type": "boolean", + "description": "boolean value denoting if the contact is a primary contact for it's tenant." }, - "leadId": { + "type": { "type": "string", - "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + "description": "type of the contact" }, - "addressId": { + "tenantId": { "type": "string", - "description": "id of the address of the tenant" + "description": "tenant id this contact belongs to" } }, "required": [ - "name", - "status", - "key", - "domains" + "firstName", + "lastName", + "email", + "isPrimary" ], "additionalProperties": false }, - "NewTenantOnboarding": { - "title": "NewTenantOnboarding", + "NewContact": { + "title": "NewContact", "type": "object", - "description": "model describing payload used to create and onboard a tenant (tsType: Omit, schemaOptions: { title: 'NewTenantOnboarding', exclude: [] })", + "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { title: 'NewContact', exclude: [ 'id' ] })", "properties": { - "contact": { - "type": "object", - "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { exclude: [ 'tenantId', 'id' ] })", - "title": "ContactExcluding_tenantId-id_", - "properties": { - "deleted": { - "type": "boolean" - }, - "deletedOn": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "createdOn": { - "type": "string", - "format": "date-time" - }, - "modifiedOn": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string" - }, - "modifiedBy": { - "type": "string" - }, - "name": { - "type": "string", - "description": "name of the contact of a tenant" - }, - "email": { - "type": "string", - "description": "email id of the contact" - }, - "isPrimary": { - "type": "boolean", - "description": "boolean value denoting if the contact is a primary contact for it's tenant." - } - }, - "required": [ - "name", - "email", - "isPrimary" - ], - "additionalProperties": false + "deleted": { + "type": "boolean" }, - "name": { - "type": "string" + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true }, - "address": { + "deletedBy": { "type": "string", - "description": "address of the tenant owners" + "nullable": true }, - "city": { + "createdOn": { "type": "string", - "description": "city of the tenant owner" + "format": "date-time" }, - "state": { + "modifiedOn": { "type": "string", - "description": "state of the tenant owner" + "format": "date-time" }, - "zip": { + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "firstName": { "type": "string", - "description": "zip code of the tenant owner" + "description": "first name of the lead" }, - "country": { + "lastName": { "type": "string", - "description": "country of the tenant owner" + "description": "last name of the lead" }, - "key": { + "email": { "type": "string", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 + "description": "email id of the contact" }, - "domains": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "format": "hostname" - } + "isPrimary": { + "type": "boolean", + "description": "boolean value denoting if the contact is a primary contact for it's tenant." + }, + "type": { + "type": "string", + "description": "type of the contact" + }, + "tenantId": { + "type": "string", + "description": "tenant id this contact belongs to" } }, "required": [ - "name", - "key", - "domains" + "firstName", + "lastName", + "email", + "isPrimary" ], "additionalProperties": false }, - "TenantOnboardDTO": { - "title": "TenantOnboardDTO", + "ContactWithRelations": { + "title": "ContactWithRelations", "type": "object", - "description": "model describing payload used to create and onboard a tenant", - "properties": { - "contact": { - "type": "object", - "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { exclude: [ 'tenantId', 'id' ] })", - "title": "ContactExcluding_tenantId-id_", - "properties": { - "deleted": { - "type": "boolean" - }, - "deletedOn": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "createdOn": { - "type": "string", - "format": "date-time" - }, - "modifiedOn": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string" - }, - "modifiedBy": { - "type": "string" - }, - "name": { - "type": "string", - "description": "name of the contact of a tenant" - }, - "email": { - "type": "string", - "description": "email id of the contact" - }, - "isPrimary": { - "type": "boolean", - "description": "boolean value denoting if the contact is a primary contact for it's tenant." - } - }, - "required": [ - "name", - "email", - "isPrimary" - ], - "additionalProperties": false - }, - "name": { - "type": "string" - }, - "address": { - "type": "string", - "description": "address of the tenant owners" - }, - "city": { - "type": "string", - "description": "city of the tenant owner" - }, - "state": { - "type": "string", - "description": "state of the tenant owner" - }, - "zip": { - "type": "string", - "description": "zip code of the tenant owner" - }, - "country": { - "type": "string", - "description": "country of the tenant owner" - }, - "key": { - "type": "string", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 - }, - "domains": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "format": "hostname" - } - } - }, - "required": [ - "name", - "key", - "domains" - ], - "additionalProperties": false - }, - "ProvisionDTO": { - "title": "ProvisionDTO", - "type": "object", - "description": "model describing payload used to provision resources for a tenant (tsType: Omit, schemaOptions: { title: 'ProvisionDTO', exclude: [] })", - "properties": { - "subscriptionId": { - "type": "string" - }, - "chargeId": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ProvisioningDTO": { - "title": "ProvisioningDTO", - "type": "object", - "description": "model describing payload used to provision resources for a tenant", - "properties": { - "subscriptionId": { - "type": "string" - }, - "chargeId": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ContactWithRelations": { - "title": "ContactWithRelations", - "type": "object", - "description": "contacts belonging to a tenant (tsType: ContactWithRelations, schemaOptions: { includeRelations: true })", + "description": "contacts belonging to a tenant (tsType: ContactWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -1933,9 +1807,13 @@ "id": { "type": "string" }, - "name": { + "firstName": { "type": "string", - "description": "name of the contact of a tenant" + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" }, "email": { "type": "string", @@ -1945,6 +1823,10 @@ "type": "boolean", "description": "boolean value denoting if the contact is a primary contact for it's tenant." }, + "type": { + "type": "string", + "description": "type of the contact" + }, "tenantId": { "type": "string", "description": "tenant id this contact belongs to" @@ -1955,16 +1837,17 @@ "foreignKey": {} }, "required": [ - "name", + "firstName", + "lastName", "email", "isPrimary" ], "additionalProperties": false }, - "ResourceWithRelations": { - "title": "ResourceWithRelations", + "ContactPartial": { + "title": "ContactPartial", "type": "object", - "description": "model for resources that are provisioned for a tenant (tsType: ResourceWithRelations, schemaOptions: { includeRelations: true })", + "description": "contacts belonging to a tenant (tsType: Partial, schemaOptions: { partial: true })", "properties": { "deleted": { "type": "boolean" @@ -1995,38 +1878,37 @@ "id": { "type": "string" }, - "externalIdentifier": { + "firstName": { "type": "string", - "description": "identifier for the resource in the external system it was provisioned" + "description": "first name of the lead" }, - "type": { + "lastName": { "type": "string", - "description": "type of the resource like storage, compute, etc" - }, - "metadata": { - "type": "object", - "description": "any type specific metadata of the resource like connection info, size, etc" + "description": "last name of the lead" }, - "tenantId": { + "email": { "type": "string", - "description": "id of the tenant for which this resource is provisioned" + "description": "email id of the contact" }, - "tenant": { - "$ref": "#/components/schemas/TenantWithRelations" + "isPrimary": { + "type": "boolean", + "description": "boolean value denoting if the contact is a primary contact for it's tenant." }, - "foreignKey": {} + "type": { + "type": "string", + "description": "type of the contact" + }, + "tenantId": { + "type": "string", + "description": "tenant id this contact belongs to" + } }, - "required": [ - "externalIdentifier", - "type", - "metadata" - ], "additionalProperties": false }, - "AddressWithRelations": { - "title": "AddressWithRelations", + "Invoice": { + "title": "Invoice", "type": "object", - "description": "this model represents the address of a company or lead (tsType: AddressWithRelations, schemaOptions: { includeRelations: true })", + "description": "this model represents an invoice with the amount and period generated for a tenant in the system", "properties": { "deleted": { "type": "boolean" @@ -2057,36 +1939,62 @@ "id": { "type": "string" }, - "address": { + "startDate": { "type": "string", - "description": "address of the company" + "description": "start date for the period this invoice is generated for" }, - "city": { + "endDate": { "type": "string", - "description": "city of the company" + "description": "end date for the period this invoice is generated for" }, - "state": { + "amount": { + "type": "number", + "description": "total amount for the invoice" + }, + "currencyCode": { "type": "string", - "description": "state of the company" + "description": "currency for the invoice" }, - "zip": { + "invoiceFile": { "type": "string", - "description": "zip code of the company" + "description": "option reference to the generated file of the invoice" }, - "country": { + "dueDate": { "type": "string", - "description": "country of the company" + "description": "due date for the invoice" + }, + "status": { + "type": "number", + "description": "status of the invoice - 0(PENDING), 1(PAID), 2(CANCELLED)", + "enum": [ + "PENDING", + "PAID", + "CANCELLED", + 0, + 1, + 2 + ] + }, + "tenantId": { + "type": "string", + "description": "id of the tenant this invoice is generated for" } }, "required": [ - "country" + "startDate", + "endDate", + "amount", + "currencyCode", + "dueDate", + "status", + "tenantId" ], "additionalProperties": false }, - "LeadWithRelations": { - "title": "LeadWithRelations", + "NewInvoice": { + "title": "NewInvoice", "type": "object", - "description": "this model represents a lead that could eventually be a tenant in the system (tsType: LeadWithRelations, schemaOptions: { includeRelations: true })", + "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: Omit, schemaOptions: { title: 'NewInvoice', exclude: [ 'id' ] })", "properties": { "deleted": { "type": "boolean" @@ -2114,49 +2022,62 @@ "modifiedBy": { "type": "string" }, - "id": { - "type": "string" - }, - "name": { - "type": "string", - "description": "name of the lead" - }, - "companyName": { + "startDate": { "type": "string", - "description": "name of the lead's company" + "description": "start date for the period this invoice is generated for" }, - "email": { + "endDate": { "type": "string", - "description": "email of the lead" + "description": "end date for the period this invoice is generated for" }, - "isValidated": { - "type": "boolean", - "description": "whether the lead`s email has been validated or not" + "amount": { + "type": "number", + "description": "total amount for the invoice" }, - "addressId": { + "currencyCode": { "type": "string", - "description": "id of the address of the lead" + "description": "currency for the invoice" }, - "tenant": { - "$ref": "#/components/schemas/TenantWithRelations" + "invoiceFile": { + "type": "string", + "description": "option reference to the generated file of the invoice" }, - "address": { - "$ref": "#/components/schemas/AddressWithRelations" + "dueDate": { + "type": "string", + "description": "due date for the invoice" }, - "foreignKey": {} - }, - "required": [ - "name", - "companyName", - "email", - "isValidated" + "status": { + "type": "number", + "description": "status of the invoice - 0(PENDING), 1(PAID), 2(CANCELLED)", + "enum": [ + "PENDING", + "PAID", + "CANCELLED", + 0, + 1, + 2 + ] + }, + "tenantId": { + "type": "string", + "description": "id of the tenant this invoice is generated for" + } + }, + "required": [ + "startDate", + "endDate", + "amount", + "currencyCode", + "dueDate", + "status", + "tenantId" ], "additionalProperties": false }, - "TenantWithRelations": { - "title": "TenantWithRelations", + "ResourceWithRelations": { + "title": "ResourceWithRelations", "type": "object", - "description": "main model of the service that represents a tenant in the system, either pooled or siloed (tsType: TenantWithRelations, schemaOptions: { includeRelations: true })", + "description": "model for resources that are provisioned for a tenant (tsType: ResourceWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -2187,79 +2108,38 @@ "id": { "type": "string" }, - "name": { - "type": "string", - "description": "name of the tenant" - }, - "status": { - "type": "number", - "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", - "enum": [ - 0, - 1, - 2, - 3, - 4, - 5 - ] - }, - "key": { + "externalIdentifier": { "type": "string", - "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 + "description": "identifier for the resource in the external system it was provisioned" }, - "spocUserId": { + "type": { "type": "string", - "description": "user id of the admin user who acts as a spoc for this tenant." - }, - "domains": { - "type": "array", - "items": { - "type": "string", - "description": "array of domains that are allowed for this tenant" - } + "description": "type of the resource like storage, compute, etc" }, - "leadId": { - "type": "string", - "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + "metadata": { + "type": "object", + "description": "any type specific metadata of the resource like connection info, size, etc" }, - "addressId": { + "tenantId": { "type": "string", - "description": "id of the address of the tenant" - }, - "contacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ContactWithRelations" - } - }, - "resources": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResourceWithRelations" - } + "description": "id of the tenant for which this resource is provisioned" }, - "lead": { - "$ref": "#/components/schemas/LeadWithRelations" + "tenant": { + "$ref": "#/components/schemas/TenantWithRelations" }, - "foreignKey": {}, - "address": { - "$ref": "#/components/schemas/AddressWithRelations" - } + "foreignKey": {} }, "required": [ - "name", - "status", - "key", - "domains" + "externalIdentifier", + "type", + "metadata" ], "additionalProperties": false }, - "TenantPartial": { - "title": "TenantPartial", + "AddressWithRelations": { + "title": "AddressWithRelations", "type": "object", - "description": "main model of the service that represents a tenant in the system, either pooled or siloed (tsType: Partial, schemaOptions: { partial: true })", + "description": "this model represents the address of a company or lead (tsType: AddressWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -2290,54 +2170,36 @@ "id": { "type": "string" }, - "name": { + "address": { "type": "string", - "description": "name of the tenant" - }, - "status": { - "type": "number", - "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", - "enum": [ - 0, - 1, - 2, - 3, - 4, - 5 - ] + "description": "address of the company" }, - "key": { + "city": { "type": "string", - "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 + "description": "city of the company" }, - "spocUserId": { + "state": { "type": "string", - "description": "user id of the admin user who acts as a spoc for this tenant." - }, - "domains": { - "type": "array", - "items": { - "type": "string", - "description": "array of domains that are allowed for this tenant" - } + "description": "state of the company" }, - "leadId": { + "zip": { "type": "string", - "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + "description": "zip code of the company" }, - "addressId": { + "country": { "type": "string", - "description": "id of the address of the tenant" + "description": "country of the company" } }, + "required": [ + "country" + ], "additionalProperties": false }, - "Lead": { - "title": "Lead", + "LeadWithRelations": { + "title": "LeadWithRelations", "type": "object", - "description": "this model represents a lead that could eventually be a tenant in the system", + "description": "this model represents a lead that could eventually be a tenant in the system (tsType: LeadWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -2368,9 +2230,13 @@ "id": { "type": "string" }, - "name": { + "firstName": { "type": "string", - "description": "name of the lead" + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" }, "companyName": { "type": "string", @@ -2387,20 +2253,28 @@ "addressId": { "type": "string", "description": "id of the address of the lead" - } + }, + "tenant": { + "$ref": "#/components/schemas/TenantWithRelations" + }, + "address": { + "$ref": "#/components/schemas/AddressWithRelations" + }, + "foreignKey": {} }, "required": [ - "name", + "firstName", + "lastName", "companyName", "email", "isValidated" ], "additionalProperties": false }, - "CreateLeadDTO": { - "title": "CreateLeadDTO", + "TenantWithRelations": { + "title": "TenantWithRelations", "type": "object", - "description": "model describing payload used to create a lead (tsType: Omit, schemaOptions: { title: 'CreateLeadDTO', exclude: [ 'isValidated', 'addressId', 'id' ] })", + "description": "main model of the service that represents a tenant in the system, either pooled or siloed (tsType: TenantWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -2428,206 +2302,84 @@ "modifiedBy": { "type": "string" }, + "id": { + "type": "string" + }, "name": { "type": "string", - "description": "name of the lead" + "description": "name of the tenant" }, - "companyName": { + "status": { + "type": "number", + "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + "key": { "type": "string", - "description": "name of the lead's company" + "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", + "pattern": "^[a-z0-9]+$", + "maxLength": 10 }, - "email": { + "spocUserId": { "type": "string", - "description": "email of the lead" + "description": "user id of the admin user who acts as a spoc for this tenant." + }, + "domains": { + "type": "array", + "items": { + "type": "string", + "description": "array of domains that are allowed for this tenant" + } + }, + "leadId": { + "type": "string", + "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + }, + "addressId": { + "type": "string", + "description": "id of the address of the tenant" + }, + "contacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContactWithRelations" + } }, + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResourceWithRelations" + } + }, + "lead": { + "$ref": "#/components/schemas/LeadWithRelations" + }, + "foreignKey": {}, "address": { - "type": "object", - "description": "this model represents the address of a company or lead (tsType: @loopback/repository-json-schema#Optional, 'country'>, schemaOptions: { exclude: [ 'id' ], optional: [ 'country' ] })", - "title": "AddressOptional_country_Excluding_id_", - "properties": { - "deleted": { - "type": "boolean" - }, - "deletedOn": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "createdOn": { - "type": "string", - "format": "date-time" - }, - "modifiedOn": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string" - }, - "modifiedBy": { - "type": "string" - }, - "address": { - "type": "string", - "description": "address of the company" - }, - "city": { - "type": "string", - "description": "city of the company" - }, - "state": { - "type": "string", - "description": "state of the company" - }, - "zip": { - "type": "string", - "description": "zip code of the company" - }, - "country": { - "type": "string", - "description": "country of the company" - } - }, - "additionalProperties": false + "$ref": "#/components/schemas/AddressWithRelations" } }, "required": [ "name", - "companyName", - "email" - ], - "additionalProperties": false - }, - "VerifyLeadResponseDTO": { - "title": "VerifyLeadResponseDTO", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "token": { - "type": "string" - } - }, - "required": [ - "id", - "token" - ], - "additionalProperties": false - }, - "LeadPartial": { - "title": "LeadPartial", - "type": "object", - "description": "this model represents a lead that could eventually be a tenant in the system (tsType: Partial, schemaOptions: { partial: true })", - "properties": { - "deleted": { - "type": "boolean" - }, - "deletedOn": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "createdOn": { - "type": "string", - "format": "date-time" - }, - "modifiedOn": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string" - }, - "modifiedBy": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string", - "description": "name of the lead" - }, - "companyName": { - "type": "string", - "description": "name of the lead's company" - }, - "email": { - "type": "string", - "description": "email of the lead" - }, - "isValidated": { - "type": "boolean", - "description": "whether the lead`s email has been validated or not" - }, - "addressId": { - "type": "string", - "description": "id of the address of the lead" - } - }, - "additionalProperties": false - }, - "TenantOnboardDto": { - "title": "TenantOnboardDto", - "type": "object", - "description": "model describing payload used to create and onboard a tenant (tsType: @loopback/repository-json-schema#Optional, 'name'>, schemaOptions: { title: 'TenantOnboardDto', exclude: [ 'contact' ], optional: [ 'name' ] })", - "properties": { - "name": { - "type": "string" - }, - "address": { - "type": "string", - "description": "address of the tenant owners" - }, - "city": { - "type": "string", - "description": "city of the tenant owner" - }, - "state": { - "type": "string", - "description": "state of the tenant owner" - }, - "zip": { - "type": "string", - "description": "zip code of the tenant owner" - }, - "country": { - "type": "string", - "description": "country of the tenant owner" - }, - "key": { - "type": "string", - "pattern": "^[a-z0-9]+$", - "maxLength": 10 - }, - "domains": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "format": "hostname" - } - } - }, - "required": [ + "status", "key", "domains" ], "additionalProperties": false }, - "Invoice": { - "title": "Invoice", + "InvoiceWithRelations": { + "title": "InvoiceWithRelations", "type": "object", - "description": "this model represents an invoice with the amount and period generated for a tenant in the system", + "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: InvoiceWithRelations, schemaOptions: { includeRelations: true })", "properties": { "deleted": { "type": "boolean" @@ -2697,7 +2449,11 @@ "tenantId": { "type": "string", "description": "id of the tenant this invoice is generated for" - } + }, + "tenant": { + "$ref": "#/components/schemas/TenantWithRelations" + }, + "foreignKey": {} }, "required": [ "startDate", @@ -2710,10 +2466,10 @@ ], "additionalProperties": false }, - "NewInvoice": { - "title": "NewInvoice", + "InvoicePartial": { + "title": "InvoicePartial", "type": "object", - "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: Omit, schemaOptions: { title: 'NewInvoice', exclude: [ 'id' ] })", + "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: Partial, schemaOptions: { partial: true })", "properties": { "deleted": { "type": "boolean" @@ -2741,6 +2497,9 @@ "modifiedBy": { "type": "string" }, + "id": { + "type": "string" + }, "startDate": { "type": "string", "description": "start date for the period this invoice is generated for" @@ -2782,21 +2541,12 @@ "description": "id of the tenant this invoice is generated for" } }, - "required": [ - "startDate", - "endDate", - "amount", - "currencyCode", - "dueDate", - "status", - "tenantId" - ], "additionalProperties": false }, - "InvoiceWithRelations": { - "title": "InvoiceWithRelations", + "Tenant": { + "title": "Tenant", "type": "object", - "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: InvoiceWithRelations, schemaOptions: { includeRelations: true })", + "description": "main model of the service that represents a tenant in the system, either pooled or siloed", "properties": { "deleted": { "type": "boolean" @@ -2827,66 +2577,305 @@ "id": { "type": "string" }, - "startDate": { - "type": "string", - "description": "start date for the period this invoice is generated for" - }, - "endDate": { - "type": "string", - "description": "end date for the period this invoice is generated for" - }, - "amount": { - "type": "number", - "description": "total amount for the invoice" - }, - "currencyCode": { - "type": "string", - "description": "currency for the invoice" - }, - "invoiceFile": { - "type": "string", - "description": "option reference to the generated file of the invoice" - }, - "dueDate": { + "name": { "type": "string", - "description": "due date for the invoice" + "description": "name of the tenant" }, "status": { "type": "number", - "description": "status of the invoice - 0(PENDING), 1(PAID), 2(CANCELLED)", + "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", "enum": [ - "PENDING", - "PAID", - "CANCELLED", 0, 1, - 2 + 2, + 3, + 4, + 5, + 6, + 7 ] }, - "tenantId": { + "key": { "type": "string", - "description": "id of the tenant this invoice is generated for" + "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", + "pattern": "^[a-z0-9]+$", + "maxLength": 10 }, - "tenant": { - "$ref": "#/components/schemas/TenantWithRelations" + "spocUserId": { + "type": "string", + "description": "user id of the admin user who acts as a spoc for this tenant." }, - "foreignKey": {} + "domains": { + "type": "array", + "items": { + "type": "string", + "description": "array of domains that are allowed for this tenant" + } + }, + "leadId": { + "type": "string", + "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + }, + "addressId": { + "type": "string", + "description": "id of the address of the tenant" + } }, "required": [ - "startDate", - "endDate", - "amount", - "currencyCode", - "dueDate", + "name", "status", - "tenantId" + "key", + "domains" ], "additionalProperties": false }, - "InvoicePartial": { - "title": "InvoicePartial", + "TenantOnboardDto": { + "title": "TenantOnboardDto", "type": "object", - "description": "this model represents an invoice with the amount and period generated for a tenant in the system (tsType: Partial, schemaOptions: { partial: true })", + "description": "model describing payload used to create and onboard a tenant (tsType: @loopback/repository-json-schema#Optional, 'name'>, schemaOptions: { title: 'TenantOnboardDto', exclude: [ 'contact' ], optional: [ 'name' ] })", + "properties": { + "name": { + "type": "string" + }, + "address": { + "type": "string", + "description": "address of the tenant owners" + }, + "city": { + "type": "string", + "description": "city of the tenant owner" + }, + "state": { + "type": "string", + "description": "state of the tenant owner" + }, + "zip": { + "type": "string", + "description": "zip code of the tenant owner" + }, + "country": { + "type": "string", + "description": "country of the tenant owner" + }, + "key": { + "type": "string", + "pattern": "^[a-z0-9]+$", + "maxLength": 10 + }, + "domains": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "format": "hostname" + } + } + }, + "required": [ + "key", + "domains" + ], + "additionalProperties": false + }, + "Lead": { + "title": "Lead", + "type": "object", + "description": "this model represents a lead that could eventually be a tenant in the system", + "properties": { + "deleted": { + "type": "boolean" + }, + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "createdOn": { + "type": "string", + "format": "date-time" + }, + "modifiedOn": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "id": { + "type": "string" + }, + "firstName": { + "type": "string", + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" + }, + "companyName": { + "type": "string", + "description": "name of the lead's company" + }, + "email": { + "type": "string", + "description": "email of the lead" + }, + "isValidated": { + "type": "boolean", + "description": "whether the lead`s email has been validated or not" + }, + "addressId": { + "type": "string", + "description": "id of the address of the lead" + } + }, + "required": [ + "firstName", + "lastName", + "companyName", + "email", + "isValidated" + ], + "additionalProperties": false + }, + "CreateLeadDTO": { + "title": "CreateLeadDTO", + "type": "object", + "description": "model describing payload used to create a lead (tsType: Omit, schemaOptions: { title: 'CreateLeadDTO', exclude: [ 'isValidated', 'addressId', 'id' ] })", + "properties": { + "deleted": { + "type": "boolean" + }, + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "createdOn": { + "type": "string", + "format": "date-time" + }, + "modifiedOn": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "firstName": { + "type": "string", + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" + }, + "companyName": { + "type": "string", + "description": "name of the lead's company" + }, + "email": { + "type": "string", + "description": "email of the lead" + }, + "address": { + "type": "object", + "description": "this model represents the address of a company or lead (tsType: @loopback/repository-json-schema#Optional, 'country'>, schemaOptions: { exclude: [ 'id' ], optional: [ 'country' ] })", + "title": "AddressOptional_country_Excluding_id_", + "properties": { + "deleted": { + "type": "boolean" + }, + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "createdOn": { + "type": "string", + "format": "date-time" + }, + "modifiedOn": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "address": { + "type": "string", + "description": "address of the company" + }, + "city": { + "type": "string", + "description": "city of the company" + }, + "state": { + "type": "string", + "description": "state of the company" + }, + "zip": { + "type": "string", + "description": "zip code of the company" + }, + "country": { + "type": "string", + "description": "country of the company" + } + }, + "additionalProperties": false + } + }, + "required": [ + "firstName", + "lastName", + "companyName", + "email" + ], + "additionalProperties": false + }, + "VerifyLeadResponseDTO": { + "title": "VerifyLeadResponseDTO", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "required": [ + "id", + "token" + ], + "additionalProperties": false + }, + "LeadPartial": { + "title": "LeadPartial", + "type": "object", + "description": "this model represents a lead that could eventually be a tenant in the system (tsType: Partial, schemaOptions: { partial: true })", "properties": { "deleted": { "type": "boolean" @@ -2914,68 +2903,268 @@ "modifiedBy": { "type": "string" }, - "id": { + "id": { + "type": "string" + }, + "firstName": { + "type": "string", + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" + }, + "companyName": { + "type": "string", + "description": "name of the lead's company" + }, + "email": { + "type": "string", + "description": "email of the lead" + }, + "isValidated": { + "type": "boolean", + "description": "whether the lead`s email has been validated or not" + }, + "addressId": { + "type": "string", + "description": "id of the address of the lead" + } + }, + "additionalProperties": false + }, + "NewTenantOnboarding": { + "title": "NewTenantOnboarding", + "type": "object", + "description": "model describing payload used to create and onboard a tenant (tsType: Omit, schemaOptions: { title: 'NewTenantOnboarding', exclude: [] })", + "properties": { + "contact": { + "type": "object", + "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { exclude: [ 'tenantId', 'id' ] })", + "title": "ContactExcluding_tenantId-id_", + "properties": { + "deleted": { + "type": "boolean" + }, + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "createdOn": { + "type": "string", + "format": "date-time" + }, + "modifiedOn": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "firstName": { + "type": "string", + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" + }, + "email": { + "type": "string", + "description": "email id of the contact" + }, + "isPrimary": { + "type": "boolean", + "description": "boolean value denoting if the contact is a primary contact for it's tenant." + }, + "type": { + "type": "string", + "description": "type of the contact" + } + }, + "required": [ + "firstName", + "lastName", + "email", + "isPrimary" + ], + "additionalProperties": false + }, + "name": { + "type": "string" + }, + "address": { + "type": "string", + "description": "address of the tenant owners" + }, + "city": { + "type": "string", + "description": "city of the tenant owner" + }, + "state": { + "type": "string", + "description": "state of the tenant owner" + }, + "zip": { + "type": "string", + "description": "zip code of the tenant owner" + }, + "country": { + "type": "string", + "description": "country of the tenant owner" + }, + "key": { + "type": "string", + "pattern": "^[a-z0-9]+$", + "maxLength": 10 + }, + "domains": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "format": "hostname" + } + } + }, + "required": [ + "name", + "key", + "domains" + ], + "additionalProperties": false + }, + "TenantOnboardDTO": { + "title": "TenantOnboardDTO", + "type": "object", + "description": "model describing payload used to create and onboard a tenant", + "properties": { + "contact": { + "type": "object", + "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { exclude: [ 'tenantId', 'id' ] })", + "title": "ContactExcluding_tenantId-id_", + "properties": { + "deleted": { + "type": "boolean" + }, + "deletedOn": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "deletedBy": { + "type": "string", + "nullable": true + }, + "createdOn": { + "type": "string", + "format": "date-time" + }, + "modifiedOn": { + "type": "string", + "format": "date-time" + }, + "createdBy": { + "type": "string" + }, + "modifiedBy": { + "type": "string" + }, + "firstName": { + "type": "string", + "description": "first name of the lead" + }, + "lastName": { + "type": "string", + "description": "last name of the lead" + }, + "email": { + "type": "string", + "description": "email id of the contact" + }, + "isPrimary": { + "type": "boolean", + "description": "boolean value denoting if the contact is a primary contact for it's tenant." + }, + "type": { + "type": "string", + "description": "type of the contact" + } + }, + "required": [ + "firstName", + "lastName", + "email", + "isPrimary" + ], + "additionalProperties": false + }, + "name": { "type": "string" }, - "startDate": { + "address": { "type": "string", - "description": "start date for the period this invoice is generated for" + "description": "address of the tenant owners" }, - "endDate": { + "city": { "type": "string", - "description": "end date for the period this invoice is generated for" - }, - "amount": { - "type": "number", - "description": "total amount for the invoice" + "description": "city of the tenant owner" }, - "currencyCode": { + "state": { "type": "string", - "description": "currency for the invoice" + "description": "state of the tenant owner" }, - "invoiceFile": { + "zip": { "type": "string", - "description": "option reference to the generated file of the invoice" + "description": "zip code of the tenant owner" }, - "dueDate": { + "country": { "type": "string", - "description": "due date for the invoice" - }, - "status": { - "type": "number", - "description": "status of the invoice - 0(PENDING), 1(PAID), 2(CANCELLED)", - "enum": [ - "PENDING", - "PAID", - "CANCELLED", - 0, - 1, - 2 - ] + "description": "country of the tenant owner" }, - "tenantId": { + "key": { "type": "string", - "description": "id of the tenant this invoice is generated for" + "pattern": "^[a-z0-9]+$", + "maxLength": 10 + }, + "domains": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "format": "hostname" + } } }, + "required": [ + "name", + "key", + "domains" + ], "additionalProperties": false }, - "Contact": { - "title": "Contact", + "SubscriptionDTO": { + "title": "SubscriptionDTO", "type": "object", - "description": "contacts belonging to a tenant", + "description": "(tsType: SubscriptionDTO, schemaOptions: { title: 'SubscriptionDTO' })", "properties": { "deleted": { "type": "boolean" }, "deletedOn": { "type": "string", - "format": "date-time", - "nullable": true + "format": "date-time" }, "deletedBy": { - "type": "string", - "nullable": true + "type": "string" }, "createdOn": { "type": "string", @@ -2994,89 +3183,47 @@ "id": { "type": "string" }, - "name": { - "type": "string", - "description": "name of the contact of a tenant" + "subscriberId": { + "type": "string" }, - "email": { - "type": "string", - "description": "email id of the contact" + "startDate": { + "type": "string" }, - "isPrimary": { - "type": "boolean", - "description": "boolean value denoting if the contact is a primary contact for it's tenant." + "endDate": { + "type": "string" }, - "tenantId": { - "type": "string", - "description": "tenant id this contact belongs to" + "status": { + "type": "number" + }, + "planId": { + "type": "string" + }, + "plan": { + "type": "object" } }, - "required": [ - "name", - "email", - "isPrimary" - ], - "additionalProperties": false + "additionalProperties": false, + "x-typescript-type": "SubscriptionDTO" }, - "NewContact": { - "title": "NewContact", + "TenantTierDTO": { + "title": "TenantTierDTO", "type": "object", - "description": "contacts belonging to a tenant (tsType: Omit, schemaOptions: { title: 'NewContact', exclude: [ 'id' ] })", + "description": "(tsType: TenantTierDTO, schemaOptions: { title: 'TenantTierDTO' })", "properties": { - "deleted": { - "type": "boolean" - }, - "deletedOn": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "deletedBy": { - "type": "string", - "nullable": true - }, - "createdOn": { - "type": "string", - "format": "date-time" - }, - "modifiedOn": { - "type": "string", - "format": "date-time" - }, - "createdBy": { - "type": "string" - }, - "modifiedBy": { + "tier": { "type": "string" - }, - "name": { - "type": "string", - "description": "name of the contact of a tenant" - }, - "email": { - "type": "string", - "description": "email id of the contact" - }, - "isPrimary": { - "type": "boolean", - "description": "boolean value denoting if the contact is a primary contact for it's tenant." - }, - "tenantId": { - "type": "string", - "description": "tenant id this contact belongs to" } }, "required": [ - "name", - "email", - "isPrimary" + "tier" ], - "additionalProperties": false + "additionalProperties": false, + "x-typescript-type": "TenantTierDTO" }, - "ContactPartial": { - "title": "ContactPartial", + "TenantPartial": { + "title": "TenantPartial", "type": "object", - "description": "contacts belonging to a tenant (tsType: Partial, schemaOptions: { partial: true })", + "description": "main model of the service that represents a tenant in the system, either pooled or siloed (tsType: Partial, schemaOptions: { partial: true })", "properties": { "deleted": { "type": "boolean" @@ -3109,23 +3256,73 @@ }, "name": { "type": "string", - "description": "name of the contact of a tenant" + "description": "name of the tenant" }, - "email": { + "status": { + "type": "number", + "description": "status of a tenant, it can be - 0(active), 1(provisioning),2(deprovisioning),3(inactive)", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + "key": { "type": "string", - "description": "email id of the contact" + "description": "a short string used to identify a tenant. This is also used as the namespace and subdomain for this particular tenant", + "pattern": "^[a-z0-9]+$", + "maxLength": 10 }, - "isPrimary": { - "type": "boolean", - "description": "boolean value denoting if the contact is a primary contact for it's tenant." + "spocUserId": { + "type": "string", + "description": "user id of the admin user who acts as a spoc for this tenant." }, - "tenantId": { + "domains": { + "type": "array", + "items": { + "type": "string", + "description": "array of domains that are allowed for this tenant" + } + }, + "leadId": { "type": "string", - "description": "tenant id this contact belongs to" + "description": "id of the lead from which this tenant was generated. this is optional as a tenant can be created without this lead." + }, + "addressId": { + "type": "string", + "description": "id of the address of the tenant" } }, "additionalProperties": false }, + "WebhookDTO": { + "title": "WebhookDTO", + "type": "object", + "description": "(tsType: WebhookDTO, schemaOptions: { title: 'WebhookDTO' })", + "properties": { + "initiatorId": { + "type": "string" + }, + "data": { + "type": "object" + }, + "type": { + "type": "number" + } + }, + "required": [ + "initiatorId", + "data", + "type" + ], + "additionalProperties": false, + "x-typescript-type": "WebhookDTO" + }, "loopback.Count": { "type": "object", "title": "loopback.Count", @@ -3271,7 +3468,10 @@ "id": { "type": "boolean" }, - "name": { + "firstName": { + "type": "boolean" + }, + "lastName": { "type": "boolean" }, "email": { @@ -3280,6 +3480,9 @@ "isPrimary": { "type": "boolean" }, + "type": { + "type": "boolean" + }, "tenantId": { "type": "boolean" } @@ -3299,9 +3502,11 @@ "createdBy", "modifiedBy", "id", - "name", + "firstName", + "lastName", "email", "isPrimary", + "type", "tenantId" ], "example": "deleted" @@ -3393,7 +3598,10 @@ "id": { "type": "boolean" }, - "name": { + "firstName": { + "type": "boolean" + }, + "lastName": { "type": "boolean" }, "email": { @@ -3402,6 +3610,9 @@ "isPrimary": { "type": "boolean" }, + "type": { + "type": "boolean" + }, "tenantId": { "type": "boolean" } @@ -3421,9 +3632,11 @@ "createdBy", "modifiedBy", "id", - "name", + "firstName", + "lastName", "email", "isPrimary", + "type", "tenantId" ], "example": "deleted" @@ -3934,7 +4147,10 @@ "id": { "type": "boolean" }, - "name": { + "firstName": { + "type": "boolean" + }, + "lastName": { "type": "boolean" }, "companyName": { @@ -3965,7 +4181,8 @@ "createdBy", "modifiedBy", "id", - "name", + "firstName", + "lastName", "companyName", "email", "isValidated", @@ -4060,7 +4277,10 @@ "id": { "type": "boolean" }, - "name": { + "firstName": { + "type": "boolean" + }, + "lastName": { "type": "boolean" }, "companyName": { @@ -4091,7 +4311,8 @@ "createdBy", "modifiedBy", "id", - "name", + "firstName", + "lastName", "companyName", "email", "isValidated", diff --git a/services/tenant-management-service/src/permissions.ts b/services/tenant-management-service/src/permissions.ts index 9da2602..46bf5df 100644 --- a/services/tenant-management-service/src/permissions.ts +++ b/services/tenant-management-service/src/permissions.ts @@ -5,6 +5,7 @@ export const PermissionKey = { ViewLead: '10203', CreateTenant: '10204', ProvisionTenant: '10216', + OffBoardTenant: '10217', UpdateTenant: '10205', DeleteTenant: '10206', ViewTenant: '10207', @@ -17,7 +18,6 @@ export const PermissionKey = { DeleteInvoice: '10214', ViewInvoice: '10215', CreateNotification: '2', - CreateSubscription: '7001', UpdateSubscription: '7002', ViewSubscription: '7004', diff --git a/services/tenant-management-service/src/services/index.ts b/services/tenant-management-service/src/services/index.ts index 587861a..658a671 100644 --- a/services/tenant-management-service/src/services/index.ts +++ b/services/tenant-management-service/src/services/index.ts @@ -5,3 +5,4 @@ export * from './aws'; export * from './provisioning.service'; export * from './notifications'; export * from './invoice-pdf-generator.service'; +export * from './off-board.service'; diff --git a/services/tenant-management-service/src/services/off-board.service.ts b/services/tenant-management-service/src/services/off-board.service.ts new file mode 100644 index 0000000..e36cdd0 --- /dev/null +++ b/services/tenant-management-service/src/services/off-board.service.ts @@ -0,0 +1,60 @@ +import {repository} from '@loopback/repository'; +import {TenantRepository, WebhookSecretRepository} from '../repositories'; +import {TenantStatus} from '../enums'; +import {inject, service} from '@loopback/core'; +import {CryptoHelperService} from './crypto-helper.service'; +import {OFFBOARDING_PIPELINES} from '../keys'; +import {TenantTierDTO} from '../models/dtos/tenant-tier-dto.model'; +import {HttpErrors} from '@loopback/rest'; +import {CodeBuildService} from './aws'; +import {ILogger, LOGGER} from '@sourceloop/core'; +import {OffBoard} from '../enums/off-board.enum'; + +export class OffBoardService { + constructor( + @service(CryptoHelperService) + private cryptoHelperService: CryptoHelperService, + @repository(TenantRepository) + private readonly tenantRepository: TenantRepository, + @repository(WebhookSecretRepository) + private webhookSecretRepo: WebhookSecretRepository, + @service(CodeBuildService) + private codeBuildService: CodeBuildService, + @inject(OFFBOARDING_PIPELINES) + private offBoardingPipelines: Record, + @inject(LOGGER.LOGGER_INJECT) + private logger: ILogger, + ) {} + + async offBoardTenant(tenantId: string, dto: TenantTierDTO) { + await this.tenantRepository.updateById(tenantId, { + status: TenantStatus.OFFBOARDING, + }); + + const hmacSecret = this.cryptoHelperService.generateRandomString(32); + + if (!this.offBoardingPipelines[dto.tier]) { + this.logger.error(`Pipeline not configured for tier: ${dto.tier}`); + throw new HttpErrors.InternalServerError(); + } + + // trigger the codebuild pipeline + const startOutput = await this.codeBuildService.startBuild( + this.offBoardingPipelines[dto.tier], + {}, + ); + + if (!startOutput.build?.id) { + throw new HttpErrors.InternalServerError('Failed to start build'); + } + + await this.webhookSecretRepo.set(`${tenantId}:offboarding`, { + secret: hmacSecret, + context: startOutput.build.id, + }); + await this.webhookSecretRepo.expire( + `${tenantId}:offboarding`, + +process.env.WEBHOOK_SECRET_EXPIRY!, + ); + } +} diff --git a/services/tenant-management-service/src/services/webhook/index.ts b/services/tenant-management-service/src/services/webhook/index.ts index b3553c8..5c9121e 100644 --- a/services/tenant-management-service/src/services/webhook/index.ts +++ b/services/tenant-management-service/src/services/webhook/index.ts @@ -1 +1,2 @@ export * from './provisioning-webhook.handler'; +export * from './offboarding-webhook.handler'; diff --git a/services/tenant-management-service/src/services/webhook/offboarding-webhook.handler.ts b/services/tenant-management-service/src/services/webhook/offboarding-webhook.handler.ts new file mode 100644 index 0000000..8ebef40 --- /dev/null +++ b/services/tenant-management-service/src/services/webhook/offboarding-webhook.handler.ts @@ -0,0 +1,152 @@ +import {inject} from '@loopback/context'; +import {Transaction, repository} from '@loopback/repository'; +import {NotificationType, TenantStatus, WebhookType} from '../../enums'; +import {ResourceRepository, TenantRepository} from '../../repositories'; +import { + IWebhookHandler, + ResourceTypes, + WebhookPayload, + WebhookStatus, +} from '../../types'; +import {NotificationService} from '../notifications'; +import {service} from '@loopback/core'; +import {HttpErrors} from '@loopback/rest'; +import {LOGGER, ILogger} from '@sourceloop/core'; +import {CryptoHelperService} from '../crypto-helper.service'; +import {OffBoardService} from '../off-board.service'; +import {TenantTierDTO} from '../../models/dtos/tenant-tier-dto.model'; +import {PermissionKey} from '../../permissions'; +import {webhookHandler} from '../../decorators'; + +@webhookHandler() +export class OffBoardingWebhookHandler implements IWebhookHandler { + type: WebhookType = WebhookType.TENANT_OFFBOARDING; + constructor( + @repository(ResourceRepository) + public resourceRepository: ResourceRepository, + @repository(TenantRepository) + public tenantRepository: TenantRepository, + @inject('services.NotificationService') + private notificationService: NotificationService, + @service(CryptoHelperService) + private cryptoHelperService: CryptoHelperService, + @service(OffBoardService) + private offBoardService: OffBoardService, + @inject(LOGGER.LOGGER_INJECT) + private readonly logger: ILogger, + ) {} + async handle(payload: WebhookPayload): Promise { + const transaction = await this.resourceRepository.beginTransaction(); + try { + const existing = await this.tenantRepository.findOne( + { + where: { + id: payload.initiatorId, + status: { + inq: [TenantStatus.OFFBOARDING, TenantStatus.OFFBOARDING_RETRY], + }, + }, + }, + { + transaction, + }, + ); + if (!existing) { + this.logger.error('Tenant not found or not in offboarding state'); + throw new HttpErrors.Unauthorized(); + } + if (payload.data.status === WebhookStatus.SUCCESS) { + await this.tenantRepository.updateById( + payload.initiatorId, + { + status: TenantStatus.INACTIVE, + }, + {transaction}, + ); + if (payload.data.resources.length > 0) { + await this.resourceRepository.deleteAll({ + tenantId: payload.initiatorId, + type: ResourceTypes.BUCKET, + }); + } + await this._sendEmail(transaction, payload); + } else { + if (existing.status === TenantStatus.OFFBOARDING_RETRY) { + await this.tenantRepository.updateById( + payload.initiatorId, + { + status: TenantStatus.ACTIVE, + }, + {transaction}, + ); + } else { + await this.tenantRepository.updateById( + payload.initiatorId, + { + status: TenantStatus.OFFBOARDING_RETRY, + }, + {transaction}, + ); + + //re-trigger the pipeline + await this.offBoardService.offBoardTenant( + payload.initiatorId, + new TenantTierDTO({tier: payload.data.tier}), + ); + } + } + await transaction.commit(); + } catch (e) { + await transaction.rollback(); + this.logger.error(e); + throw e; + } + } + + private async _sendEmail(transaction: Transaction, payload: WebhookPayload) { + 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, + ); + } + } +} diff --git a/services/tenant-management-service/src/types/webhook-payload.type.ts b/services/tenant-management-service/src/types/webhook-payload.type.ts index 9c68a12..f98db87 100644 --- a/services/tenant-management-service/src/types/webhook-payload.type.ts +++ b/services/tenant-management-service/src/types/webhook-payload.type.ts @@ -1,6 +1,6 @@ import {AnyObject} from '@loopback/repository'; import {WebhookType} from '../enums/webhook-types.enum'; -import {NotificationType} from '../enums'; +import {NotificationType, PlanTier} from '../enums'; export enum WebhookStatus { FAILURE, @@ -22,7 +22,7 @@ export type ResourceProvisionedWebhookPayload = { /** * The type of the webhook. */ - type: WebhookType.RESOURCES_PROVISIONED; + type: WebhookType; /** * The data of the webhook. */ @@ -30,9 +30,12 @@ export type ResourceProvisionedWebhookPayload = { status: WebhookStatus.SUCCESS | WebhookStatus.FAILURE; resources: AnyObject[]; appPlaneUrl: string; + tier: PlanTier; }; }; +export type SecretInfo = {secret: string; context: string}; + // export interface WebhookNotificationServiceType{ // send(email: string, type: NotificationType, data: WebNotifiactionDataType, token: string):Promise // } diff --git a/services/tenant-management-service/src/webhook.component.ts b/services/tenant-management-service/src/webhook.component.ts index 3abd10a..7916d65 100644 --- a/services/tenant-management-service/src/webhook.component.ts +++ b/services/tenant-management-service/src/webhook.component.ts @@ -63,13 +63,23 @@ import { } from './repositories'; import {WebhookVerifierProvider} from './interceptors'; import {SystemUserProvider} from './providers'; -import {CryptoHelperService, NotificationService} from './services'; +import { + AWS_CODEBUILD_CLIENT, + CodebuildClientProvider, + CodeBuildService, + CryptoHelperService, + NotificationService, + OffBoardService, +} from './services'; import { DEFAULT_SIGNATURE_HEADER, DEFAULT_TIMESTAMP_HEADER, DEFAULT_TIMESTAMP_TOLERANCE, } from './utils'; -import {ProvisioningWebhookHandler} from './services/webhook'; +import { + OffBoardingWebhookHandler, + ProvisioningWebhookHandler, +} from './services/webhook'; export class WebhookTenantManagementServiceComponent implements Component { constructor( @@ -140,7 +150,11 @@ export class WebhookTenantManagementServiceComponent implements Component { }), Binding.bind('services.NotificationService').toClass(NotificationService), createServiceBinding(ProvisioningWebhookHandler), + createServiceBinding(OffBoardingWebhookHandler), + createServiceBinding(OffBoardService), createServiceBinding(CryptoHelperService), + createServiceBinding(CodeBuildService), + Binding.bind(AWS_CODEBUILD_CLIENT).toProvider(CodebuildClientProvider), ]; // this.services=[ProvisioningWebhookHandler,CryptoHelperService]; }