UNPKG

@sap/cds-mtxs

Version:

SAP Cloud Application Programming Model - Multitenancy library

127 lines (105 loc) 4.89 kB
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?._, true) } }) 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, schema } = 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), ...(schema ? { schema } : {}) })) } catch (e) { if (e.message === 'ENTITY_ALREADY_EXISTS' || e.code === 301 || e.message.match(/unique constraint/i)) { await t0_(async () => { const row = await SELECT.one.from(Tenants).columns('metadata').where({ ID: tenant }) let existingMetadata = {} try { existingMetadata = JSON.parse(row.metadata || '{}'); } catch (error) { LOG.error('Failed to parse metadata to JSON', { error, metadata }); } const merged = JSON.stringify(cds.utils.merge(existingMetadata, metadata)) await UPSERT.into(Tenants, { ID: tenant, metadata: merged, ...(schema ? { schema } : {}) }) }) } else throw e } LOG.info('subscribed tenant', tenant) }) // ds.after(['upgrade', 'extend'], async (_, req) => { // const { tenant } = req.data // if (tenant === t0) return // const version = JSON.stringify({ // version: require(cds.root + '/package.json').version // }) // await t0_(UPSERT.into(Tenants, { ID: tenant, version })) // LOG.info('upgraded 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.prepend(() => { 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.Tenants', cds.env.sql.names || 'plain', csn, 'hana') : 'cds_xt_Tenants' } // 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('./hana/hdi-ctnr-mgr').defaultInstance() // 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 const effectiveParams = JSON.parse(JSON.stringify(params ?? {})) // clone to preserve params for original tenant subscription if (effectiveParams?.hdi?.create) delete effectiveParams.hdi.create.hana_tenant_id // never use hana_tenant_id for t0 await ds.tx({ tenant: t0 }, async tx => { const csn = await cds.load(`${__dirname}/../../db/t0.cds`) const columns = await tx.getColumns(t0, _getT0TenantsTableName(csn), effectiveParams) const needsT0Redeployment = !columns.some(c => c.toLowerCase() === 'version') if (!needsT0Redeployment) return await tx.subscribe({ tenant: t0, options: { csn, _: effectiveParams } }) }) } cds.once('served', () => _resubscribeT0IfNeeded()) })