diff --git a/xmpls/messaging/app/incidents/field.cds b/xmpls/messaging/app/incidents/field.cds index 9368152c..5a9643b9 100644 --- a/xmpls/messaging/app/incidents/field.cds +++ b/xmpls/messaging/app/incidents/field.cds @@ -1,5 +1,5 @@ -using ProcessorService as service from '../../srv/services.cds'; -using from './annotations.cds'; +using ProcessorService as service from '../../../../srv/processor-service.cds'; +using from '../../../../app/incidents/annotations'; annotate service.Incidents with @( /*adding email to the object page enables users to view the changes recieved via Messaging/Eventing*/ diff --git a/xmpls/messaging/package.json b/xmpls/messaging/package.json index 35c3e92a..875bf823 100644 --- a/xmpls/messaging/package.json +++ b/xmpls/messaging/package.json @@ -6,11 +6,8 @@ "@sap-cloud-sdk/http-client": "^3.6.0", "@sap-cloud-sdk/resilience": "^3.6.0", "@sap-cloud-sdk/util": "^3.6.0", - "@sap/cds": ">=7", - "@cap-js/hana": ">=1", - "@sap/xssec": "^3", + "@sap/cds": ">=8", "express": "^4", - "passport": "^0", "@sap/xb-msg-amqp-v100": "^0" }, "devDependencies": { @@ -22,27 +19,12 @@ "scripts": { "watch": "cds watch", "start": "cds-serve", - "test": "npx jest --silent", - "add-change-tracking": "npm add @cap-js/change-tracking && cp xmpls/change-tracking.cds ./srv && cp xmpls/change-tracking.test.js ./test", - "add-telemetry": "npm add @cap-js/telemetry", - "add-attachments": "npm add @cap-js/attachments && cp xmpls/attachments.cds ./srv && cp -r xmpls/content ./db/data/content", - "clone-add-attachments": "git clone https://github.com/cap-js/attachments.git && cp -r attachments/xmpl/db . && cp -r attachments/xmpl/app . && npm add https://github.com/cap-js/attachments.git", - "add-notifications": "npm add @cap-js/notifications && cp xmpls/alert-notifications.js ./srv && cp xmpls/notification-types.json ./srv", - "add-audit-log": "npm add @cap-js/audit-logging && cp xmpls/data-privacy.cds ./srv && cp xmpls/audit-log.test.js ./test", - "add-remote-service": "cp -r xmpls/remote-service/* .", - "add-messaging": "cp -r xmpls/messaging/* .", - "add-all-xmpls": "npm run add-remote-service && npm run add-messaging && npm run add-change-tracking && npm run add-audit-log", - "reset": "read -p 'This will irreversibly reset your working directory including ALL files in this git repo. Continue?' -n 1 -r && echo && if [[ $REPLY =~ ^[Yy]$ ]]; then git clean -fd && git reset --hard && npm i; fi" + "test": "npx jest --silent" }, "private": true, "sapux": [ "app/incidents" ], - "jest": { - "modulePathIgnorePatterns": [ - "/xmpls/" - ] - }, "cds": { "requires": { "incidents-app": { @@ -59,24 +41,6 @@ "kind": "cloudfoundry" } }, - "[development]": { - "auth": { - "kind": "mocked", - "users": { - "alice": { - "roles": [ - "support", - "admin" - ] - }, - "bob": { - "roles": [ - "support" - ] - } - } - } - }, "db": { "kind": "sql" }, diff --git a/xmpls/messaging/server.js b/xmpls/messaging/server.js new file mode 100644 index 00000000..452f35e0 --- /dev/null +++ b/xmpls/messaging/server.js @@ -0,0 +1,9 @@ +const cds = require ('@sap/cds') + +// Add routes to UI +cds.once('bootstrap',(app)=>{ + if (cds.watched) app.serve ('/incidents') .from ('../..', '../../app/incidents/webapp') +}) + +// Returning cds.server +module.exports = cds.server \ No newline at end of file diff --git a/xmpls/messaging/services.cds b/xmpls/messaging/services.cds index ebe38490..31974bab 100644 --- a/xmpls/messaging/services.cds +++ b/xmpls/messaging/services.cds @@ -1,3 +1,6 @@ -// Using local service implementation +// Using local service implementation with remote service using { ProcessorService } from '../../app/services'; -annotate ProcessorService with @impl: 'srv/services.js'; +annotate ProcessorService with @impl: 'srv/processor-service.js'; + +// add the additional field to display changes to email on UI +using from './app/incidents/field'; \ No newline at end of file diff --git a/xmpls/messaging/srv/processor-service.js b/xmpls/messaging/srv/processor-service.js new file mode 100644 index 00000000..6e7ef471 --- /dev/null +++ b/xmpls/messaging/srv/processor-service.js @@ -0,0 +1,119 @@ +const cds = require('@sap/cds') + +class ProcessorService extends cds.ApplicationService { + async init() { + const { Incidents } = this.entities + + this.before('UPDATE', Incidents, async req => { + let closed = await SELECT.one(1).from(req.subject).where`status.code = 'C'` + if (closed) req.reject`Can't modify a closed incident!` + }) + + this.before(['CREATE', 'UPDATE'], Incidents, req => { + let urgent = req.data.title?.match(/urgent/i) + if (urgent) req.data.urgency_code = 'H' + }) + + this.on('READ', 'Customers', req => this.onCustomerRead(req)) + this.on(['CREATE', 'UPDATE'], 'Incidents', (req, next) => this.onCustomerCache(req, next)) + this.S4bupa = await cds.connect.to('API_BUSINESS_PARTNER') + this.remoteService = await cds.connect.to('RemoteService') // REVISIT: What is this for? + + // Added Handlers for Eventing on top of remote service sample + this.messaging = await cds.connect.to('messaging') + this.messaging.on( + 'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1', + async ({ event, data }) => await this.onBusinessPartnerChanged(event, data) + ) + + return super.init() + } + async onBusinessPartnerChanged(event, data) { + const { Customers } = this.entities + const { BusinessPartnerAddress } = this.remoteService.entities + console.log('<< received', event, data) + const Id = data.BusinessPartner + const customer = await this.S4bupa.run( + SELECT.one(BusinessPartnerAddress, address => { + address('*'), + address.email(emails => { + emails('*') + }) + }).where({ BusinessPartner: Id }) + ) + if (customer) { + customer.email = customer.email[0]?.email + const result = await cds.run(UPDATE(Customers).where({ ID: customer.ID }).set({ email: customer.email })) + console.log('result', result) + } + } + + async onCustomerCache(req, next) { + const { Customers } = this.entities + const newCustomerId = req.data.customer_ID + const result = await next() + const { BusinessPartner } = this.remoteService.entities + if (newCustomerId && newCustomerId !== '' && (req.event == 'CREATE' || req.event == 'UPDATE')) { + console.log('>> CREATE or UPDATE customer!') + + // Expands are required as the runtime does not support path expressions for remote services + const customer = await this.S4bupa.run( + SELECT.one(BusinessPartner, bp => { + bp('*'), + bp.addresses(address => { + address('email', 'phoneNumber'), + address.email(emails => { + emails('email') + }), + address.phoneNumber(phoneNumber => { + phoneNumber('phone') + }) + }) + }).where({ ID: newCustomerId }) + ) + + if (customer) { + customer.email = customer.addresses[0]?.email[0]?.email + customer.phone = customer.addresses[0]?.phoneNumber[0]?.phone + delete customer.addresses + delete customer.name + await UPSERT.into(Customers).entries(customer) + } + } + return result + } + + async onCustomerRead(req) { + console.log('>> delegating to S4 service...', req.query) + const top = parseInt(req._queryOptions?.$top) || 100 + const skip = parseInt(req._queryOptions?.$skip) || 0 + + const { BusinessPartner } = this.remoteService.entities + + // Expands are required as the runtime does not support path expressions for remote services + let result = await this.S4bupa.run( + SELECT.from(BusinessPartner, bp => { + bp('*'), + bp.addresses(address => { + address('email'), + address.email(emails => { + emails('email') + }) + }) + }).limit(top, skip) + ) + + result = result.map(bp => ({ + ID: bp.ID, + name: bp.name, + email: bp.addresses[0]?.email[0]?.email + })) + + // Explicitly set $count so the values show up in the value help in the UI + result.$count = 1000 + console.log('after result', result) + return result + } +} + +module.exports = { ProcessorService } diff --git a/xmpls/messaging/srv/services.js b/xmpls/messaging/srv/services.js deleted file mode 100644 index f5f71897..00000000 --- a/xmpls/messaging/srv/services.js +++ /dev/null @@ -1,123 +0,0 @@ -/** -* Same as Remote Service Sample. Added additional Handlers for recieving and -* handling events/messages -*/ -const cds = require('@sap/cds') - -class ProcessorService extends cds.ApplicationService { - /** Registering custom event handlers */ - async init() { - this.before("UPDATE", "Incidents", (req) => this.onUpdate(req)); - this.before("CREATE", "Incidents", (req) => this.changeUrgencyDueToSubject(req.data)); - this.on('READ', 'Customers', (req) => this.onCustomerRead(req)); - this.on(['CREATE','UPDATE'], 'Incidents', (req, next) => this.onCustomerCache(req, next)); - this.S4bupa = await cds.connect.to('API_BUSINESS_PARTNER'); - this.remoteService = await cds.connect.to("RemoteService"); // REVISIT: What is this for? - - // Added Handlers for Eventing on top of remote service sample - this.messaging = await cds.connect.to('messaging'); - this.messaging.on('sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1', async ({ event, data }) => await this.onBusinessPartnerChanged(event, data)) - return super.init(); - } - - async onBusinessPartnerChanged(event, data){ - const {Customers} = this.entities; - const {BusinessPartnerAddress} = this.remoteService.entities; - console.log('<< received', event, data) - const Id = data.BusinessPartner; - const customer = await this.S4bupa.run(SELECT.one(BusinessPartnerAddress, address => { - address('*'), - address.email(emails => { - emails('*')}) - }).where({BusinessPartner: Id})); - if(customer){ - customer.email = customer.email[0]?.email - const result= await cds.run(UPDATE(Customers).where({ID: customer.ID}).set({email:customer.email})); - console.log("result",result); - } - } - - async onCustomerCache(req, next) { - const { Customers } = this.entities; - const newCustomerId = req.data.customer_ID; - const result = await next(); - const { BusinessPartner } = this.remoteService.entities; - if (newCustomerId && (newCustomerId !== "") && ((req.event == "CREATE") || (req.event == "UPDATE"))) { - console.log('>> CREATE or UPDATE customer!'); - - // Expands are required as the runtime does not support path expressions for remote services - const customer = await this.S4bupa.run(SELECT.one(BusinessPartner, bp => { - bp('*'), - bp.addresses(address => { - address('email', 'phoneNumber'), - address.email(emails => { - emails('email') - }), - address.phoneNumber(phoneNumber => { - phoneNumber('phone') - }) - }) - }).where({ ID: newCustomerId })); - - if(customer) { - customer.email = customer.addresses[0]?.email[0]?.email; - customer.phone = customer.addresses[0]?.phoneNumber[0]?.phone; - delete customer.addresses; - delete customer.name; - await UPSERT.into(Customers).entries(customer); - } - } - return result; - } - - - async onCustomerRead(req) { - console.log('>> delegating to S4 service...', req.query); - const top = parseInt(req._queryOptions?.$top) || 100; - const skip = parseInt(req._queryOptions?.$skip) || 0; - - const { BusinessPartner } = this.remoteService.entities; - - // Expands are required as the runtime does not support path expressions for remote services - let result = await this.S4bupa.run(SELECT.from(BusinessPartner, bp => { - bp('*'), - bp.addresses(address => { - address('email'), - address.email(emails => { - emails('email'); - }); - }) - }).limit(top, skip)); - - result = result.map((bp) => ({ - ID: bp.ID, - name: bp.name, - email: bp.addresses[0]?.email[0]?.email - })); - - // Explicitly set $count so the values show up in the value help in the UI - result.$count = 1000; - console.log("after result", result); - return result; - } - - - changeUrgencyDueToSubject(data) { - if (data) { - const incidents = Array.isArray(data) ? data : [data]; - incidents.forEach((incident) => { - if (incident.title?.toLowerCase().includes("urgent")) { - incident.urgency = { code: "H", descr: "High" }; - } - }); - } - } - - /** Custom Validation */ - async onUpdate (req) { - const { status_code } = await SELECT.one(req.subject, i => i.status_code).where({ID: req.data.ID}) - if (status_code === 'C') - return req.reject(`Can't modify a closed incident`) - } -} -module.exports = { ProcessorService } \ No newline at end of file diff --git a/xmpls/readme.md b/xmpls/readme.md index 1f743d01..ebd2ae3a 100644 --- a/xmpls/readme.md +++ b/xmpls/readme.md @@ -58,5 +58,4 @@ Overwrite existing files. ## Messaging -Copy all files from the `messaging` folder into the main project. -Overwrite existing files. \ No newline at end of file +Start messaging with `cds w xmpls/messaging` \ No newline at end of file