From c3c9dae80d3d4ac8d22e2c77cf3e134961fcab38 Mon Sep 17 00:00:00 2001 From: nkaputnik Date: Tue, 26 Jul 2022 09:45:34 +0200 Subject: [PATCH] First push --- bookshop/db/schema.cds | 18 ++- .../handlers/AdminService.Authors.CREATE.js | 10 +- .../handlers/AdminService.Authors.READ.js | 5 + .../handlers/AdminService.Books.CREATE.js | 9 ++ bookshop/handlers/AdminService.Books.READ.js | 6 + bookshop/package.json | 10 +- bookshop/sqlite.db | Bin 0 -> 86016 bytes bookshop/srv/admin-service.cds | 14 +- bookshop/srv/admin-service.js | 143 +++++++++++++----- bookshop/test/requests.http | 57 ++++--- 10 files changed, 203 insertions(+), 69 deletions(-) create mode 100644 bookshop/handlers/AdminService.Authors.READ.js create mode 100644 bookshop/handlers/AdminService.Books.CREATE.js create mode 100644 bookshop/handlers/AdminService.Books.READ.js create mode 100644 bookshop/sqlite.db diff --git a/bookshop/db/schema.cds b/bookshop/db/schema.cds index ec8b119a..915a9289 100644 --- a/bookshop/db/schema.cds +++ b/bookshop/db/schema.cds @@ -1,7 +1,8 @@ -using { Currency, managed, sap } from '@sap/cds/common'; +using { Currency, managed, sap, extensible } from '@sap/cds/common'; namespace sap.capire.bookshop; -entity Books : managed { +@Extensibility.Any.Enabled : true +entity Books : managed, extensible { key ID : Integer; title : localized String(111); descr : localized String(1111); @@ -11,15 +12,26 @@ entity Books : managed { price : Decimal; currency : Currency; image : LargeBinary @Core.MediaType : 'image/png'; + authorName: String; } -entity Authors : managed { +@Extensibility : { + Fields.Enabled : true, + Relations.Enabled : false, + Annotations.Enabled : true, + Logic.Enabled : true, + Logic.constraints: true, + Logic.calculations: true, + Logic.Handler : [create, update, delete, read] +} +entity Authors : managed, extensible { key ID : Integer; name : String(111); dateOfBirth : Date; dateOfDeath : Date; placeOfBirth : String; placeOfDeath : String; + virtual age: Integer; books : Association to many Books on books.author = $self; } diff --git a/bookshop/handlers/AdminService.Authors.CREATE.js b/bookshop/handlers/AdminService.Authors.CREATE.js index 67f096cf..fc03731f 100644 --- a/bookshop/handlers/AdminService.Authors.CREATE.js +++ b/bookshop/handlers/AdminService.Authors.CREATE.js @@ -1,13 +1,13 @@ async function run() { - debugger - while (true) {} - process.exit() + //debugger + //while (true) {} + //process.exit() //1.substring() let res = await cds.read(SELECT.one`title`.from(`Books`).where(`ID=201`)) let { title } = res const data = req.data - data.modifiedBy = "Custom Event handler read changed this!"; - data.placeOfDeath = ' --- Somewhere over ' + title + ' --- create in Sandbox' + data.modifiedBy = "Custom Event handler read changed this!" + data.placeOfDeath = " --- Somewhere over " + title + " --- create in Sandbox" return data } output = run() diff --git a/bookshop/handlers/AdminService.Authors.READ.js b/bookshop/handlers/AdminService.Authors.READ.js new file mode 100644 index 00000000..44f1f6f6 --- /dev/null +++ b/bookshop/handlers/AdminService.Authors.READ.js @@ -0,0 +1,5 @@ +const result_ = Array.isArray(result) ? result : [result] +for (const row of result_) { + row.modifiedBy += " --- read in sandbox" + row.age = 27 +} diff --git a/bookshop/handlers/AdminService.Books.CREATE.js b/bookshop/handlers/AdminService.Books.CREATE.js new file mode 100644 index 00000000..ab9eab58 --- /dev/null +++ b/bookshop/handlers/AdminService.Books.CREATE.js @@ -0,0 +1,9 @@ + +async function run() { + const {stock, price, author_ID} = req.data + if (stock<0) return req.reject('409', 'Stock must not be negative') + if (price<0) return req.reject('409', 'Price must not be negative') + let {name} = await SELECT.one`name`.from(`Authors`).where({ID: author_ID}) + req.data.authorName=name +} +output = run() diff --git a/bookshop/handlers/AdminService.Books.READ.js b/bookshop/handlers/AdminService.Books.READ.js new file mode 100644 index 00000000..1c946e87 --- /dev/null +++ b/bookshop/handlers/AdminService.Books.READ.js @@ -0,0 +1,6 @@ +const result_ = Array.isArray(result) ? result : [result]; +for (const row of result_) { + if (row.stock > 50) { + row.title += " ---Order now for a 10% discount!"; + } +} diff --git a/bookshop/package.json b/bookshop/package.json index f2fb4210..1141a6e4 100644 --- a/bookshop/package.json +++ b/bookshop/package.json @@ -12,7 +12,8 @@ "dependencies": { "@sap/cds": ">=5.9", "express": "^4.17.1", - "passport": ">=0.4.1" + "passport": ">=0.4.1", + "vm2": ">=3.9.9" }, "scripts": { "genres": "cds serve test/genres.cds", @@ -22,8 +23,11 @@ "cds": { "requires": { "db": { - "kind": "sql" + "kind": "sqlite", + "credentials": { + "database": "sqlite.db" + } } } } -} +} \ No newline at end of file diff --git a/bookshop/sqlite.db b/bookshop/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..c5f05ec2aecce21fdb508a4a9d903fca7819a83a GIT binary patch literal 86016 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 IcfrvAAEjU+qW}N^ literal 0 HcmV?d00001 diff --git a/bookshop/srv/admin-service.cds b/bookshop/srv/admin-service.cds index ea9b0731..d3fd5a9e 100644 --- a/bookshop/srv/admin-service.cds +++ b/bookshop/srv/admin-service.cds @@ -1,5 +1,13 @@ -using { sap.capire.bookshop as my } from '../db/schema'; -service AdminService @(requires:'admin') { - entity Books as projection on my.Books; +using {sap.capire.bookshop as my} from '../db/schema'; + +service AdminService // @(requires : 'admin') +{ + entity Books as projection on my.Books; entity Authors as projection on my.Authors; + action renameAuthor(author : Authors:ID, newName : String); + + event newBook : { + book : Books:ID; + name : Books:title + }; } diff --git a/bookshop/srv/admin-service.js b/bookshop/srv/admin-service.js index d02133d8..7cb498a9 100644 --- a/bookshop/srv/admin-service.js +++ b/bookshop/srv/admin-service.js @@ -1,77 +1,144 @@ -const cds = require("@sap/cds"); -const { VM, VMScript } = require("vm2"); -const fs = require("fs"); -const path = require("path"); +const cds = require("@sap/cds") +const cds_sandbox = require("sap/cds/sandbox") +const { VM, VMScript } = require("vm2") +const fs = require("fs") +const path = require("path") +const { nextTick } = require("process") class AdminService extends cds.ApplicationService { init() { - const { Books, Authors } = cds.entities("sap.capire.bookshop"); - this.after("READ", async (result, req) => { - const code = getCode(req, "READ"); - if (code) { - await executeCode(code, req, result); + if (!(result === undefined || result == null)) { + const code = getCode(req.target.name, "READ") + if (code) { + await executeCode(code, req, result) + } } - }); - - this.after("READ", "ListOfBooks", (each) => { - if (each.stock > 111) each.title += ` -- 11% discount!`; - }); + }) this.before("CREATE", async (req) => { - const code = getCode(req, "CREATE"); + const code = getCode(req.target.name, "CREATE") if (code) { - await executeCode(code, req); - //console.log(req.data) + await executeCode(code, req) } - }); + }) - //this.before("NEW", "Authors", genid); - //this.before("NEW", "Books", genid); - return super.init(); + this.before("UPDATE", async (req) => { + const code = getCode(req.target.name, "CREATE") + if (code) { + await executeCode(code, req) + } + }) + + this.on("*", async (req, next) => { + if (!(req.target === undefined || req.target == null)) return next() + //ToDo: check whether action or event is part of an extension + if (req.constructor.name === "EventMessage") { + const code = getCode(req.event, "ON") + if (code) { + await executeCode(code, req) + } + } else if (req.constructor.name === "ODataRequest") { + var output = {} + const code = getCode(this.name + "." + req.event, "ON") + if (code) { + await executeCode(code, req, {}, output) + return output + } + } + }) + + //ToDo: Prefix for Service not in event emitter + this.before("CREATE", "Authors", async (req) => { + let Author = req.data + await this.emit("createdAuthor", { Author }) + + }) + + return super.init() } } -function getCode(req, operation) { - const filename = req.target.name + "." + operation + ".js"; - const file = path.join(__dirname, "..", "handlers", filename); +var counter = 1; + +function newLabel() {return "VM2 - req: " + counter++} + +function getCode(name, operation) { + const filename = name + "." + operation + ".js" + const file = path.join(__dirname, "..", "handlers", filename) try { - const code = fs.readFileSync(file, "utf8"); - return code; + const code = fs.readFileSync(file, "utf8") + return code } catch (error) { - return ""; + return "" } } function scanCode(code) { //ESLINT } +/* +Base assumption: event handlers will always use publicly available application API's (services) +Inbound data for validations + - this could be a document --> req.target plus expand on related data + - event facade could have an explicit publishing of specific services or documents (e.g. remote services) + - CQN Protocol adapter for subsequent reads --> req.data plus application service calls + - what is the CDS subset to put in? + - req.data + target-rec (proxy, unloaded) + - ORM type lazy loading (dereferenced) + - application developer could actually provide custom proxies for specific functions + - performance impact of multiple accesses to object graph and multiple DB roundtrips + - can static code checking or developer annotations influence what is loaded into a graph? + - alternative: Stripped-down SELECT limited to req.target and ID + - application service only + - access rights of user respected + - What about to-many relationships? For compositions essential, for associations to be questioned + - Application Service Reads + - outbound data for changes + - call remote services + - register new remote services dynamically + - CAP provides an API on remote services - connect doesn't need to be done by extension developer + - alternative: declarative remote services plumbing with CDS service facade + - model looks like static internal services, remote calls done transparently behind the scenes -async function executeCode(code, req, result) { - let output = {}; - console.time("vm2"); + -Emit Events + + - choreography of extension points + - deep inserts vs. fine grained operations + - input validation may be suited for fine grained operations + - today not in scope for performance reasons + - two different use case: Insert new page to book vs. update order-header with items-constraints in place + - reject request, return errors and warnings - suitable for UI, too + +*/ +async function executeCode(code, req, result, output) { + const label=newLabel() + console.time(label) const vm = new VM({ console: "inherit", - timeout: 1000, + timeout: 500, allowAsync: true, sandbox: { req, result, output, cds, SELECT, INSERT, UPDATE, CREATE, JSON }, - }); - + }) + try { await vm.run(code) + return output } catch (error) { - req.reject('409','Error in VM') console.log(error) + req.reject("409", "Error in VM") + } + finally { + console.timeEnd(label) } // console.log(req.data) - console.timeEnd("vm2"); } /** Generate primary keys for target entity in request */ async function genid(req) { const { ID } = await cds .tx(req) - .run(SELECT.one.from(req.target).columns("max(ID) as ID")); - req.data.ID = ID - (ID % 100) + 100 + 1; + .run(SELECT.one.from(req.target).columns("max(ID) as ID")) + req.data.ID = ID - (ID % 100) + 100 + 1 } -module.exports = { AdminService }; +module.exports = { AdminService } diff --git a/bookshop/test/requests.http b/bookshop/test/requests.http index 7fddca0f..867a426c 100644 --- a/bookshop/test/requests.http +++ b/bookshop/test/requests.http @@ -1,10 +1,46 @@ @server = http://localhost:4004 @me = Authorization: Basic {{$processEnv USER}}: +@id = 1113 + + +### ------------------------------------------------------------------------ +# Fetch Authors +GET {{server}}/admin/Authors + +### ------------------------------------------------------------------------ +# Fetch one Author +GET {{server}}/admin/Authors({{id}}) + +### ------------------------------------------------------------------------ +# Create Author +POST {{server}}/admin/Authors +Content-Type: application/json;IEEE754Compatible=true + +{ + "ID": {{id}}, + "name": "Nick", + "placeOfBirth": "Somewhere", + "placeOfDeath": "over the Rainbox", + "dateOfBirth" : "1975-05-27" +} + +### ------------------------------------------------------------------------ +# rename author via unbound action +POST {{server}}/admin/renameAuthor +Content-Type: application/json +{{me}} + +{ "author":{{id}}, "newName":"Super Nick" } ### ------------------------------------------------------------------------ # Get service info -GET {{server}}/browse +GET {{server}}/admin +{{me}} + +### ------------------------------------------------------------------------ +# Get $metadata document +GET {{server}}/admin/$metadata {{me}} @@ -23,21 +59,8 @@ GET {{server}}/browse/ListOfBooks? {{me}} -### ------------------------------------------------------------------------ -# Fetch Authors as admin -GET {{server}}/admin/Authors(307) -### ------------------------------------------------------------------------ -# Create Author -POST {{server}}/admin/Authors -Content-Type: application/json;IEEE754Compatible=true -{ - "ID": 317, - "name": "Vitaly", - "placeOfDeath": "", - "placeOfBirth": "" -} ### ------------------------------------------------------------------------ # Fetch Books as admin @@ -50,12 +73,12 @@ Content-Type: application/json;IEEE754Compatible=true Authorization: Basic alice: { - "ID": 13, - "title": "Deh3", + "ID": 16, + "title": "Deh4", "descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.", "author": { "ID": 101 }, "genre": { "ID": 12 }, - "stock": 100, + "stock": -100, "price": "12.05", "currency": { "code": "USD" } }