Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin does stuff based on app or mtx sidecar #24

Open
wants to merge 2 commits into
base: multitenant-support
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 132 additions & 105 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,130 +2,157 @@ const cds = require('@sap/cds/lib')
const LOG = cds.log('attachments')
const DEBUG = LOG._debug ? LOG.debug : undefined

cds.on('loaded', function UnfoldModel (csn) {
if ('Attachments' in csn.definitions) ; else return
// in mtx sidecar, cds.env.requires.multitenancy is falsy but cds.env.requires["cds.xt.DeploymentService"] is truthy
const is_mtx_sidecar = cds.env.requires['cds.xt.DeploymentService']

/*
* tenant lifecycle management stuff, e.g., create s3 bucket for tenant
*/
if (cds.env.requires.multitenancy || is_mtx_sidecar) {
cds.on('listening', async () => {
const ds = await cds.connect.to('cds.xt.DeploymentService')
ds.after('subscribe', async (_, req) => {
req.on('succeeded', async () => {
const { tenant } = req.data
await cds.tx({ tenant }, async tx => {
const { isdir } = cds.utils,
_content = isdir('../../db/content')
if (!_content) return
const { join } = cds.utils.path
const { readFile } = cds.utils.promises
const _init = a => readFile(join(_content, a.filename)).then(c => (a.content = c))
const attachments = await SELECT`ID, filename, mimeType, subject, note, createdAt, createdBy`
.from`sap.common.Attachments`.where`content is null`
if (!attachments.length) return
await Promise.all(attachments.map(_init))
await DELETE.from`sap.common.Attachments`.where`content is null`
await tx.run(await INSERT(attachments).into('sap.common.Attachments'))
})
})
})
})
}

/*
* model extension stuff (also needs to be done in mtx sidecar to extend the base model for extensions)
*/
cds.on('loaded', function UnfoldModel(csn) {
if ('Attachments' in csn.definitions);
else return
cds.linked(csn).forall('Composition', comp => {
if (comp._target['@_is_media_data'] && comp.parent && comp.is2many) {
let Facets = comp.parent['@UI.Facets']; if (!Facets) return
let Facets = comp.parent['@UI.Facets']
if (!Facets) return
DEBUG?.('Adding @UI.Facet to:', comp.parent.name)
Facets.push({
$Type : 'UI.ReferenceFacet', Target: `${comp.name}/@UI.LineItem`,
Label : '{i18n>Attachments}',
$Type: 'UI.ReferenceFacet',
Target: `${comp.name}/@UI.LineItem`,
Label: '{i18n>Attachments}'
})
}
})
})

// if (cds.env.requires.multitenancy){
// cds.on("listening", async () => {
// const ds = await cds.connect.to("cds.xt.DeploymentService");
// ds.after("subscribe", async (_, req) => {
// req.on("succeeded", async () => {
// const { tenant } = req.data;
// await cds.tx({ tenant }, async (tx) => {
// const { isdir } = cds.utils,_content = isdir("../../db/content");
// if (!_content) return;
// const { join } = cds.utils.path;
// const { readFile } = cds.utils.promises;
// const _init = (a) => readFile(join(_content, a.filename)).then((c) => (a.content = c));
// const attachments = await SELECT`ID, filename, mimeType, subject, note, createdAt, createdBy` .from`sap.common.Attachments`.where`content is null`;
// if (!attachments.length) return;
// await Promise.all(attachments.map(_init));
// await DELETE.from`sap.common.Attachments`.where`content is null`;
// await tx.run(await INSERT(attachments).into("sap.common.Attachments"));
// });
// });
// });
// });
// }
/*
* service enhancemets that are not needed in mtx sidecar
*/
if (!is_mtx_sidecar) {
cds.once('served', async function PluginHandlers() {
if ('Attachments' in cds.model.definitions);
else return

const AttachmentsSrv = await cds.connect.to('attachments')

// Tagging sap.common.Images and all derivates of it
cds.model.definitions['sap.common.Images']._is_images = true

cds.once('served', async function PluginHandlers () {

if ('Attachments' in cds.model.definitions) ; else return
const AttachmentsSrv = await cds.connect.to('attachments')

// Tagging sap.common.Images and all derivates of it
cds.model.definitions['sap.common.Images']._is_images = true

// Searching all associations to attachments to add respective handlers
for (let srv of cds.services) {
if (srv instanceof cds.ApplicationService) {
Object.values(srv.entities) .forEach (entity => {
let any=0; for (let e in entity.elements) {
if (e === 'SiblingEntity') continue // REVISIT: Why do we have this?
const element = entity.elements[e], target = element._target
if (target?.['@_is_media_data']) {
DEBUG?.('serving attachments for:', target.name)
const handler = target._is_images ? ReadImage : ReadAttachment
for (let each of [target, target.drafts]) if (each) srv.after ("READ", each, handler)
// srv.on ("NEW", entity, AddAttachmentHandler(element))
srv.after ("SAVE", entity, DraftSaveHandler4(element))
any++
}
}
// Add handler to render image urls in objec pages
if (any) srv.after ("READ", entity, AddImageUrl)
})
}
}
// Searching all associations to attachments to add respective handlers
for (let srv of cds.services) {
if (srv instanceof cds.ApplicationService) {
Object.values(srv.entities).forEach(entity => {
let any = 0
for (let e in entity.elements) {
if (e === 'SiblingEntity') continue // REVISIT: Why do we have this?
const element = entity.elements[e],
target = element._target
if (target?.['@_is_media_data']) {
DEBUG?.('serving attachments for:', target.name)
const handler = target._is_images ? ReadImage : ReadAttachment
for (let each of [target, target.drafts]) if (each) srv.after('READ', each, handler)
// srv.on ("NEW", entity, AddAttachmentHandler(element))
srv.after('SAVE', entity, DraftSaveHandler4(element))
any++
}
}
// Add handler to render image urls in objec pages
if (any) srv.after('READ', entity, AddImageUrl)
})
}
}

async function AddImageUrl (results, req) {
if (results.length !== 1) return
// Add image urls
// TODO: Generalize this by rewriting getElementsOfType() function according to simplified model
const imageElements = [['customer', 'avatar']]
const [result] = results
for (const element of imageElements) {
const [k, v] = element
if (result[k]) {
const ID = result[k].ID
if (!result[k][v]) result[k] = Object.assign(result[k], { [v]: {}, })
const baseUrl = req?.req?.baseUrl || 'http://localhost:4004'
const baseEntity = cds.model.definitions[req.entity].elements[k].target.split('.')[1]
const url = `${baseUrl}/${baseEntity}(${ID})/${v}/$value`
result[k][v].url = url
result[k][v]['[email protected]'] = url
async function AddImageUrl(results, req) {
if (results.length !== 1) return
// Add image urls
// TODO: Generalize this by rewriting getElementsOfType() function according to simplified model
const imageElements = [['customer', 'avatar']]
const [result] = results
for (const element of imageElements) {
const [k, v] = element
if (result[k]) {
const ID = result[k].ID
if (!result[k][v]) result[k] = Object.assign(result[k], { [v]: {} })
const baseUrl = req?.req?.baseUrl || 'http://localhost:4004'
const baseEntity = cds.model.definitions[req.entity].elements[k].target.split('.')[1]
const url = `${baseUrl}/${baseEntity}(${ID})/${v}/$value`
result[k][v].url = url
result[k][v]['[email protected]'] = url
}
}
}
}

async function ReadImage ([attachment], req) {
if (!req._path?.endsWith('$value')) return
if (!attachment.content) {
let keys = { ID: req.params.at(-1) }
attachment.content = await AttachmentsSrv.get (req.target,keys)
async function ReadImage([attachment], req) {
if (!req._path?.endsWith('$value')) return
if (!attachment.content) {
let keys = { ID: req.params.at(-1) }
attachment.content = await AttachmentsSrv.get(req.target, keys)
}
}
}

async function ReadAttachment ([attachment], req) {
if (!req._path?.endsWith('content')) return
if (!attachment.content && req.target.isDraft) { // if not found, read attachment from active data...
let keys = req.params.at(-1)
attachment.content = await AttachmentsSrv.get (req.target.actives,keys)
async function ReadAttachment([attachment], req) {
if (!req._path?.endsWith('content')) return
if (!attachment.content && req.target.isDraft) {
// if not found, read attachment from active data...
let keys = req.params.at(-1)
attachment.content = await AttachmentsSrv.get(req.target.actives, keys)
}
}
}

/**
* Returns a handler to copy updated attachments content from draft to active / object store
*/
function DraftSaveHandler4 (composition) {
const Attachments = composition._target
const keys_and_content = _keys4(Attachments) .map (k => ({ref:[k]})) .concat ({ref:['content']})
return async (_, req) => {
// REVISIT: The below query loads the attachments into buffers -> needs streaming instead
const attachments = await SELECT (keys_and_content) .from (Attachments.drafts) .where ([
...req.subject.ref[0].where.map(x => x.ref ? {ref:['up_',...x.ref]} : x),
'and', {ref:['content']}, 'is not null' // NOTE: needs skip LargeBinary fix to Lean Draft
])
if (attachments.length) await AttachmentsSrv.put (Attachments, attachments)
/**
* Returns a handler to copy updated attachments content from draft to active / object store
*/
function DraftSaveHandler4(composition) {
const Attachments = composition._target
const keys_and_content = _keys4(Attachments)
.map(k => ({ ref: [k] }))
.concat({ ref: ['content'] })
return async (_, req) => {
// REVISIT: The below query loads the attachments into buffers -> needs streaming instead
const attachments = await SELECT(keys_and_content)
.from(Attachments.drafts)
.where([
...req.subject.ref[0].where.map(x => (x.ref ? { ref: ['up_', ...x.ref] } : x)),
'and',
{ ref: ['content'] },
'is not null' // NOTE: needs skip LargeBinary fix to Lean Draft
])
if (attachments.length) await AttachmentsSrv.put(Attachments, attachments)
}
}
}

function _keys4 (Attachments) {
let { up_ } = Attachments.keys
if (up_) return up_.keys.map(k => 'up__'+k.ref[0]).concat('filename')
else return Object.keys(Attachments.keys)
}
})
function _keys4(Attachments) {
let { up_ } = Attachments.keys
if (up_) return up_.keys.map(k => 'up__' + k.ref[0]).concat('filename')
else return Object.keys(Attachments.keys)
}
})
}