@sap/cds-mtxs
Version:
SAP Cloud Application Programming Model - Multitenancy library
101 lines (82 loc) • 3.65 kB
JavaScript
const cds = require('@sap/cds/lib')
const { t0_ } = require('../../lib/utils')
const Tenants = 'cds.xt.Tenants'
const LOG = cds.log('mtx')
const main = require('../config')
exports.activated = 'Generic Metadata'
const t0 = cds.env.requires.multitenancy?.t0 ?? 't0'
// Add database-agnostic metadata handlers to DeploymentService...
cds.on ('serving:cds.xt.DeploymentService', ds => {
const lazyT0 = cds.env.requires['cds.xt.DeploymentService']?.lazyT0 ?? cds.env.requires.multitenancy?.lazyT0
ds.before ('*', req => {
const { tenant } = req?.data ?? {}
if (tenant) cds.context = { tenant }
})
ds.before ('subscribe', req => {
if (lazyT0 && req.data.tenant !== t0) {
return _resubscribeT0IfNeeded(req.data.options?._)
}
})
ds.before ('upgrade', async req => {
if (main.requires.extensibility) return // no checks needed
if (cds.env.requires['cds.xt.DeploymentService']?.upgrade?.skipExtensionCheck === true) return
// duplicate code, but it must be ensured that the tenant is set for the following operations
const { tenant } = req?.data ?? {}
if (tenant) cds.context = { tenant }
let existingExt
try {
existingExt = await SELECT.one(1).from('cds.xt.Extensions')
} catch (e) {
LOG.debug('No extensions found', e) // ok, no problem
}
if (existingExt) cds.error(`Extensions exist, but extensibility is disabled. Upgrade aborted to avoid data loss`, { status: 500 })
})
ds.after ('subscribe', async (_, req) => {
const { tenant, metadata } = req.data
if (tenant === t0) return
try {
// can't use UPSERT here so @cds.on.insert still works for createdAt
await t0_(INSERT.into(Tenants, { ID: tenant, metadata: JSON.stringify(metadata) }))
} catch (e) {
if (e.message === 'ENTITY_ALREADY_EXISTS') {
await t0_(UPSERT.into(Tenants, { ID: tenant, metadata: JSON.stringify(metadata) }))
} else throw e
}
LOG.info(`subscribed tenant ${tenant}`)
})
ds.after ('unsubscribe', async (_, req) => {
const { tenant } = req.data
if (tenant !== t0) await t0_(DELETE.from(Tenants).where({ ID: tenant }))
LOG.info(`unsubscribed tenant ${tenant}`)
})
ds.on ('getTenants', async () => {
return (await cds.tx({ tenant: t0 }, tx =>
tx.run(SELECT.from(Tenants, tenant => { tenant.ID }))
)).map(({ ID }) => ID)
})
const { getArtifactCdsPersistenceName } = require('@sap/cds-compiler')
function _getT0TenantsTableName(csn) {
return cds.requires.db.kind === 'hana' ?
getArtifactCdsPersistenceName('cds.xt.Jobs', cds.env.sql.names || 'plain', csn, 'hana')
: 'cds_xt_Jobs'
}
// Needs to be exposed for lazyT0 (CALM use case)
const _resubscribeT0IfNeeded = module.exports.resubscribeT0IfNeeded = async function (params, onlyDeploy) {
if (lazyT0 && cds.requires.db.kind === 'hana' && !onlyDeploy) {
const hana = require('../plugins/hana/srv-mgr') // REVISIT: workaround for lazyT0
try { await hana.get(t0, { disableCache: true }) } catch (e) {
if (e.status === 404) return
else throw e
}
}
await cds.connect() // REVISIT: Ideally shouldn't be necessary
await ds.tx({ tenant: t0 }, async tx => {
const csn = await cds.load(`${__dirname}/../../db/t0.cds`)
const columns = await tx.getColumns(t0, _getT0TenantsTableName(csn), params)
const needsT0Redeployment = !columns.includes('modifiedAt') && !columns.includes('MODIFIEDAT')
if (!needsT0Redeployment) return
await tx.subscribe({ tenant: t0, options: { csn, _: params }})
})
}
cds.once('served', () => _resubscribeT0IfNeeded())
})