Skip to content

Commit

Permalink
Multi-tenancy - get all tenants/ configurations (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
fraliv13 committed Jan 19, 2024
1 parent 619a8eb commit e15177b
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-trains-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@totalsoft/multitenancy-core': minor
---

Get all tenants/configurations
40 changes: 38 additions & 2 deletions packages/multitenancy-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ const { tenantConfiguration } = require("@totalsoft/multitenancy-core")
//}
const tenantSpecifficValue = tenantConfiguration.getValue('3c841325-eccc-4670-a577-09546df7b1fc', "tenantProp")
```
The `getAll` function retrieves all tenant configurations from environment variables:

```javascript
const { tenantConfiguration } = require("@totalsoft/multitenancy-core")

// process.env = {
// IS_MULTITENANT: 'true',
// MultiTenancy__Tenants__Tenant1__TenantId: '3c841325-eccc-4670-a577-09546df7b1fc',
// MultiTenancy__Tenants__Tenant1__Name: 'tenant 1 name',
// MultiTenancy__Tenants__Tenant1__Enabled: 'true',
// MultiTenancy__Tenants__Tenant2__TenantId: '9c841325-eccc-4670-a577-09546df7b1fc',
// MultiTenancy__Tenants__Tenant2__Name: 'tenant 2 name',
// MultiTenancy__Tenants__Tenant2__Enabled: 'false',
//}
const tenants = tenantConfiguration.getAll();
```
The `getConnectionInfo` extension reads the configuration to return a connection information object:
```javascript
const { tenantConfiguration } = require("@totalsoft/multitenancy-core")
Expand Down Expand Up @@ -92,20 +108,40 @@ const { tenantService } = require("@totalsoft/multitenancy-core")
// IS_MULTITENANT: 'true',
// MultiTenancy__Tenants__Tenant1__TenantId: tenantId,
// MultiTenancy__Tenants__Tenant1__Name: 'Tenant 1 name'
}
//}

const res = await tenantService.getTenantFromId(tenantId)
```

### tenantService.getAll

The `getAll` function retrieves all enabled tenants:

```javascript
const { tenantService } = require("@totalsoft/multitenancy-core")

// process.env = {
// IS_MULTITENANT: 'true',
// MultiTenancy__Tenants__Tenant1__TenantId: '3c841325-eccc-4670-a577-09546df7b1fc',
// MultiTenancy__Tenants__Tenant1__Name: 'tenant 1 name',
// MultiTenancy__Tenants__Tenant1__Enabled: 'true',
// MultiTenancy__Tenants__Tenant2__TenantId: '9c841325-eccc-4670-a577-09546df7b1fc',
// MultiTenancy__Tenants__Tenant2__Name: 'tenant 2 name',
// MultiTenancy__Tenants__Tenant2__Enabled: 'false',
//}
const tenants = await tenantService.getAll();
```

The returned `Tenant` object has the following structure:
```typescript
export interface Tenant {
id: string,
code: string,
name?: string
enabled: boolean
}
```
The service throws an exception when the tenant with the specified id is not found.
This function is useful when you need to retrieve all enabled tenants at once, for example, when initializing your application or when performing bulk operations on all tenants.

### tenantContextAccessor
Allows propagating a tenant context across async/await calls.
Expand Down
28 changes: 28 additions & 0 deletions packages/multitenancy-core/__tests__/tenant-configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,32 @@ describe('logging plugin tests:', () => {
expect(connectionInfo.password).toBe(pass)
expect(connectionInfo.otherParams).toBe(otherParams)
})

it('should load all tenant configurations:', () => {
// Arrange
const tenants = [
{ tenantId: '3c841325-eccc-4670-a577-09546df7b1fc', tenantProp: 'tenant 1 speciffic prop', code: 'tenant1' },
{ tenantId: '9a841325-eccc-4670-a577-09546df7b1fc', tenantProp: 'tenant 2 speciffic prop', code: 'tenant2' }
]

process.env = {
IS_MULTITENANT: 'true',
...tenants.reduce(
(env, { tenantId, tenantProp, code }, _) => ({
...env,
[`MultiTenancy__Tenants__${code}__TenantId`]: tenantId,
[`MultiTenancy__Tenants__${code}__TenantProp`]: tenantProp
}),
{}
)
}

const { tenantConfiguration } = require('../src')

// Act
const res = tenantConfiguration.getAll()

// Assert
expect(res).toEqual(tenants)
})
})
53 changes: 53 additions & 0 deletions packages/multitenancy-core/__tests__/tenant-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,57 @@ describe('tenant service tests:', () => {
///Assert
await expect(action).rejects.toThrowError('disabled')
})

it('should load tenants', async () => {
// Arrange
const tenants = [
{ id: '3c841325-eccc-4670-a577-09546df7b1fc', name: 'tenant 1 name', code: 'tenant1', enabled: true },
{ id: '9c841325-eccc-4670-a577-09546df7b1fc', name: 'tenant 2 name', code: 'tenant2', enabled: true }
]

process.env = {
IS_MULTITENANT: 'true',
...tenants.reduce(
(env, { id, name, code }) => ({
...env,
[`MultiTenancy__Tenants__${code}__TenantId`]: id,
[`MultiTenancy__Tenants__${code}__Name`]: name
}),
{}
)
}

const { tenantService } = require('../src')

// Act
const res = await tenantService.getAll()

// Assert
expect(res).toEqual(tenants)
})

it('should return an empty array if no tenants are enabled', async () => {
// Arrange
const tenant = {
id: '3c841325-eccc-4670-a577-09546df7b1fc',
name: 'tenant 1 name',
code: 'tenant1',
enabled: 'false'
}

process.env = {
IS_MULTITENANT: 'true',
[`MultiTenancy__Tenants__${tenant.code}__TenantId`]: tenant.id,
[`MultiTenancy__Tenants__${tenant.code}__Name`]: tenant.name,
[`MultiTenancy__Tenants__${tenant.code}__Enabled`]: tenant.enabled
}

const { tenantService } = require('../src')

// Act
const res = await tenantService.getAll()

// Assert
expect(res).toHaveLength(0)
})
})
24 changes: 23 additions & 1 deletion packages/multitenancy-core/src/tenantConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import objectPath from 'object-path'
import humps from 'humps'
import debounce from 'debounce'
import deepmerge from 'deepmerge'
import { TenantMapByCode, TenantMapById } from './types'
import { TenantMapByCode, TenantMapById, TenantSection } from './types'
const { IS_MULTITENANT } = process.env
const isMultiTenant = JSON.parse(IS_MULTITENANT || 'false')
const debounceTimeoutMs = 5000
Expand Down Expand Up @@ -33,6 +33,28 @@ export function getValue(tenantId: string, key?: string) {
return tenantValue || defaultValue
}

/**
* Retrieves all tenant configurations.
* If multi-tenancy is disabled, an empty array is returned.
* If multi-tenancy is enabled, the function merges the default configuration with each tenant configuration
* and returns an array of merged configurations.
* @returns An array of TenantSection objects representing the merged tenant configurations.
*/
export function getAll(): TenantSection[] {
if (!isMultiTenant) {
return []
}

const defaults = _getDefaultsDebounced()
const allTenantsMap = _getTenantsDebounced()

return Object.entries(allTenantsMap).map(([tid, tenantSection]) => {
return _isObject(defaults) && _isObject(tenantSection)
? deepmerge(defaults, tenantSection)
: tenantSection || defaults
})
}

function _getDefaults(): TenantMapByCode {
const defaultsPrefix = 'MultiTenancy__Defaults__'
const defaults = _loadFromEnv(defaultsPrefix)
Expand Down
11 changes: 11 additions & 0 deletions packages/multitenancy-core/src/tenantService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ export async function getTenantFromId(tenantId: string): Promise<Tenant | null>
}
return tenant
}

/**
* Retrieves all enabled tenants.
* @returns A promise that resolves to an array of Tenant objects.
*/
export async function getAll(): Promise<Tenant[]> {
const configTenants = tenantConfiguration.getAll()
return configTenants
.filter(tenant => tenant.enabled !== 'false')
.map(({ tenantId: id, name, code }) => ({ id, name, code, enabled: true }))
}
1 change: 1 addition & 0 deletions packages/multitenancy-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface TenantMapByCode {
export interface TenantSection {
tenantId: string
code: string
[key: string]: any
}

export interface TenantContext {
Expand Down

0 comments on commit e15177b

Please sign in to comment.