Merge branch 'main' into main
This commit is contained in:
@@ -18,6 +18,7 @@ app.use('/-/:tarball', (req,res,next) => {
|
||||
const { tarball } = req.params
|
||||
const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
|
||||
fs.lstat(tarball,(err => {
|
||||
if (err) console.debug (`npm pack ../${pkg}`)
|
||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
||||
else next()
|
||||
}))
|
||||
@@ -49,7 +50,7 @@ app.get('/*', (req,res)=>{
|
||||
"name": package.name,
|
||||
"version": package.version,
|
||||
"dist": {
|
||||
"tarball": `/-/${tarball}`
|
||||
"tarball": `${server.url}/-/${tarball}`
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -61,7 +62,7 @@ app.get('/*', (req,res)=>{
|
||||
})
|
||||
|
||||
const server = app.listen(port, ()=>{
|
||||
const url = `http://localhost:${server.address().port}`
|
||||
const url = server.url = `http://localhost:${server.address().port}`
|
||||
console.log (`npm set ${scope}:registry=${url}`)
|
||||
exec(`npm set ${scope}:registry=${url}`)
|
||||
console.log (`${scope} registry listening on ${url}`)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const cds = require('@sap/cds')
|
||||
const { Books } = cds.entities ('sap.capire.bookshop')
|
||||
|
||||
class CatalogService extends cds.ApplicationService { init(){
|
||||
|
||||
const { Books } = cds.entities ('sap.capire.bookshop')
|
||||
|
||||
// Reduce stock of ordered books if available stock suffices
|
||||
this.on ('submitOrder', async req => {
|
||||
const {book,quantity} = req.data
|
||||
|
||||
39
bookstore/package.json
Normal file
39
bookstore/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@capire/bookstore",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@capire/bookshop": "*",
|
||||
"@capire/reviews": "*",
|
||||
"@capire/orders": "*",
|
||||
"@capire/common": "*",
|
||||
"@sap/cds": "^5",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"ReviewsService": {
|
||||
"kind": "odata",
|
||||
"model": "@capire/reviews"
|
||||
},
|
||||
"OrdersService": {
|
||||
"kind": "odata",
|
||||
"model": "@capire/orders"
|
||||
},
|
||||
"messaging": {
|
||||
"[development]": { "kind": "file-based-messaging" },
|
||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
||||
"[production]": { "kind": "enterprise-messaging" }
|
||||
},
|
||||
"db": {
|
||||
"kind": "sql",
|
||||
"[development]": {
|
||||
"model": "db/sqlite"
|
||||
},
|
||||
"[production]": {
|
||||
"model": "db/hana"
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": { "service": true }
|
||||
}
|
||||
}
|
||||
21
bookstore/server.js
Normal file
21
bookstore/server.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
// Add mashup logic
|
||||
cds.once('served', require('./srv/mashup'))
|
||||
|
||||
// Add routes to UIs from imported packages
|
||||
cds.once('bootstrap',(app)=>{
|
||||
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
||||
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
||||
app.serve ('/orders') .from('@capire/orders','app/orders')
|
||||
})
|
||||
|
||||
// Add Swagger UI
|
||||
require('./srv/swagger-ui')
|
||||
|
||||
// Returning cds.server
|
||||
module.exports = cds.server
|
||||
|
||||
// For didactic reasons in capire
|
||||
const { ReviewsService, OrdersService } = cds.requires
|
||||
if (!ReviewsService.credentials && !OrdersService.credentials) cds.requires.messaging = false
|
||||
@@ -1,6 +1,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Mashing up imported models...
|
||||
// Enhancing bookshop with Reviews and Orders provided through
|
||||
// respective reuse packages and services
|
||||
//
|
||||
|
||||
using { sap.capire.bookshop.Books } from '@capire/bookshop';
|
||||
@@ -8,19 +9,24 @@ using { sap.capire.bookshop.Books } from '@capire/bookshop';
|
||||
//
|
||||
// Extend Books with access to Reviews and average ratings
|
||||
//
|
||||
|
||||
using { ReviewsService.Reviews } from '@capire/reviews';
|
||||
extend Books with {
|
||||
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
||||
numberOfReviews : Integer;
|
||||
rating : Decimal;
|
||||
numberOfReviews : Integer;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Extend Orders with Books as Products
|
||||
//
|
||||
|
||||
using { sap.capire.orders.Orders_Items } from '@capire/orders';
|
||||
extend Orders_Items with {
|
||||
book : Association to Books on product.ID = book.ID
|
||||
using { sap.capire.orders.Orders } from '@capire/orders';
|
||||
extend Orders with {
|
||||
extend Items with {
|
||||
book : Association to Books on product.ID = book.ID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add orders fiori app (in case of embedded orders service)
|
||||
using from '@capire/orders/app/fiori';
|
||||
@@ -1,6 +1,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Mashing up provided and required services...
|
||||
// Mashing up bookshop services with required services...
|
||||
//
|
||||
module.exports = async()=>{ // called by server.js
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = async()=>{ // called by server.js
|
||||
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
||||
console.debug ('> delegating request to ReviewsService')
|
||||
const [id] = req.params, { columns, limit } = req.query.SELECT
|
||||
return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
||||
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
||||
}))
|
||||
|
||||
//
|
||||
@@ -37,13 +37,12 @@ module.exports = async()=>{ // called by server.js
|
||||
})
|
||||
|
||||
//
|
||||
// Update Books' average ratings when ReviewsService signals updatd reviews
|
||||
// Update Books' average ratings when ReviewsService signals updated reviews
|
||||
//
|
||||
ReviewsService.on ('reviewed', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
const { subject, count, rating } = msg.data
|
||||
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
||||
// ^ Note: the framework will execute this and take care for db.tx
|
||||
})
|
||||
|
||||
//
|
||||
10
bookstore/srv/swagger-ui.js
Normal file
10
bookstore/srv/swagger-ui.js
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||
const cds = require ('@sap/cds')
|
||||
try {
|
||||
const cds_swagger = require ('cds-swagger-ui-express')
|
||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
||||
} catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
@bookshop = http://localhost:4004
|
||||
@reviews-service = {{bookshop}}/reviews
|
||||
# Uncomment this when running a separate reviews service
|
||||
@reviews-service = http://localhost:4005/reviews
|
||||
# @reviews-service = http://localhost:4005/reviews
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# cds.requires.messaging.kind = file-based-messaging
|
||||
PORT = 4004
|
||||
@@ -1,4 +1,4 @@
|
||||
using { AdminService } from '../../db/schema';
|
||||
using { AdminService } from '@capire/bookshop';
|
||||
using from '../common'; // to help UI linter get the complete annotations
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
|
||||
</head>
|
||||
@@ -1,3 +0,0 @@
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=reviews/index.html">
|
||||
</head>
|
||||
@@ -6,7 +6,4 @@ using from './admin/fiori-service';
|
||||
using from './browse/fiori-service';
|
||||
using from './common';
|
||||
|
||||
using from '@capire/common';
|
||||
|
||||
// only works in case of embedded orders service
|
||||
using from '@capire/orders/app/orders/fiori-service';
|
||||
using from '@capire/bookstore/srv/mashup';
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
"name": "@capire/fiori",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@capire/bookshop": "*",
|
||||
"@capire/reviews": "*",
|
||||
"@capire/orders": "*",
|
||||
"@capire/common": "*",
|
||||
"@capire/bookstore": "*",
|
||||
"@sap/cds": "^5",
|
||||
"express": "^4.17.1",
|
||||
"passport": "^0.4.1"
|
||||
@@ -15,9 +12,6 @@
|
||||
"watch": "cds watch"
|
||||
},
|
||||
"cds": {
|
||||
"hana": {
|
||||
"deploy-format": "hdbtable"
|
||||
},
|
||||
"requires": {
|
||||
"auth": {
|
||||
"strategy": "dummy"
|
||||
@@ -30,20 +24,14 @@
|
||||
"kind": "odata",
|
||||
"model": "@capire/orders"
|
||||
},
|
||||
"db": {
|
||||
"kind": "sql",
|
||||
"[development]": {
|
||||
"model": "db/sqlite"
|
||||
},
|
||||
"[production]": {
|
||||
"model": "db/hana"
|
||||
}
|
||||
},
|
||||
"messaging": {
|
||||
"[production]": { "kind": "enterprise-messaging" },
|
||||
"[development]": { "kind": "file-based-messaging" },
|
||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
||||
"kind": "enterprise-messaging"
|
||||
"[hybrid!]": { "kind": "enterprise-messaging-shared" }
|
||||
},
|
||||
"hana": {
|
||||
"deploy-format": "hdbtable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1 @@
|
||||
const cds = require ('@sap/cds')
|
||||
module.exports = cds.server
|
||||
|
||||
cds.once('bootstrap',(app)=>{
|
||||
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
|
||||
app.use ('/bookshop', _from('@capire/bookshop/app/vue/index.html'))
|
||||
app.use ('/reviews', _from('@capire/reviews/app/vue/index.html'))
|
||||
})
|
||||
|
||||
cds.once('served', require('./srv/mashup'))
|
||||
|
||||
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||
try {
|
||||
const cds_swagger = require ('cds-swagger-ui-express')
|
||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
||||
} catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper for serving static content from npm-installed packages
|
||||
const {static} = require('express')
|
||||
const {dirname} = require('path')
|
||||
const _from = target => static (dirname (require.resolve(target)))
|
||||
module.exports = require('@capire/bookstore/server.js')
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"start:ts": "cds-ts serve srv/world.cds"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.12.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^16.11.6",
|
||||
"ts-jest": "^27.0.2",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Request } from "@sap/cds/apis/services"
|
||||
|
||||
module.exports = class say {
|
||||
hello(req: any) {
|
||||
hello(req: Request) {
|
||||
return `Hello ${req.data.to} from a TypeScript file!`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
|
||||
|
||||
using { OrdersService } from '../../srv/orders-service';
|
||||
using { OrdersService } from '../srv/orders-service';
|
||||
|
||||
|
||||
@odata.draft.enabled
|
||||
@@ -68,7 +68,7 @@ annotate OrdersService.Orders with @(
|
||||
|
||||
|
||||
|
||||
annotate OrdersService.Orders_Items with @(
|
||||
annotate OrdersService.Orders.Items with @(
|
||||
UI: {
|
||||
LineItem: [
|
||||
{Value: product_ID, Label:'Product ID'},
|
||||
@@ -1,5 +0,0 @@
|
||||
/*
|
||||
This model controls what gets served to Fiori frontends...
|
||||
*/
|
||||
|
||||
using from './orders/fiori-service';
|
||||
@@ -3,21 +3,22 @@ namespace sap.capire.orders;
|
||||
|
||||
entity Orders : cuid, managed {
|
||||
OrderNo : String @title:'Order Number'; //> readable key
|
||||
Items : Composition of many Orders_Items on Items.up_ = $self;
|
||||
Items : Composition of many {
|
||||
key ID : UUID;
|
||||
product : Association to Products;
|
||||
quantity : Integer;
|
||||
title : String; //> intentionally replicated as snapshot from product.title
|
||||
price : Double; //> materialized calculated field
|
||||
};
|
||||
buyer : User;
|
||||
currency : Currency;
|
||||
}
|
||||
|
||||
entity Orders_Items {
|
||||
key ID : UUID;
|
||||
up_ : Association to Orders;
|
||||
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
|
||||
quantity : Integer;
|
||||
title : String; //> intentionally replicated as snapshot from product.title
|
||||
price : Double;
|
||||
}
|
||||
|
||||
/** This is a stand-in for arbitrary ordered Products */
|
||||
entity Products @(cds.persistence.skip:'always') {
|
||||
key ID : String;
|
||||
}
|
||||
|
||||
|
||||
// this is to ensure we have filled-in currencies
|
||||
using from '@capire/common';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "@capire/orders",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@capire/common": "*",
|
||||
"@sap/cds": "^5"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ class OrdersService extends cds.ApplicationService {
|
||||
|
||||
/** register custom handlers */
|
||||
init(){
|
||||
const { Orders_Items:OrderItems } = this.entities
|
||||
const { 'Orders.Items':OrderItems } = this.entities
|
||||
|
||||
this.before ('UPDATE', 'Orders', async function(req) {
|
||||
const { ID, Items } = req.data
|
||||
|
||||
10924
package-lock.json
generated
10924
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
|
||||
"author": "daniel.hutzel@sap.com",
|
||||
"dependencies": {
|
||||
"@capire/bookstore": "./bookstore",
|
||||
"@capire/bookshop": "./bookshop",
|
||||
"@capire/common": "./common",
|
||||
"@capire/fiori": "./fiori",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
cds.requires.messaging.kind = file-based-messaging
|
||||
# cds.requires.messaging.kind = file-based-messaging
|
||||
PORT = 4005
|
||||
@@ -1,5 +1,5 @@
|
||||
subject;rating;title;text
|
||||
201;5;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
201;4;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
||||
207;2;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
||||
251;3;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
||||
subject;rating;reviewer;title;text
|
||||
201;5;adam;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
||||
207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
||||
251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
||||
|
@@ -10,15 +10,14 @@
|
||||
"@sap/cds": "^5",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"reviews-service": "cds watch",
|
||||
"books-reviewed": "cds watch ../reviewed"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"db": {
|
||||
"kind": "sql"
|
||||
}
|
||||
"messaging": {
|
||||
"[development]": { "kind": "file-based-messaging" },
|
||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
||||
"[production]": { "kind": "enterprise-messaging" }
|
||||
},
|
||||
"db": { "kind": "sql" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,7 @@ service ReviewsService {
|
||||
annotate ReviewsService.Reviews with @restrict:[
|
||||
{ grant:'READ', to:'any' }, // everybody can read reviews
|
||||
{ grant:'CREATE', to:'authenticated-user' }, // users must login to add reviews
|
||||
/////////////////////////////////////////////////
|
||||
//
|
||||
// Temporarily disabling this due to glitch in CAP Node.js runtime:
|
||||
// { grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' },
|
||||
// -> reenable it when the issue is fixed
|
||||
{ grant:'UPDATE', to:'authenticated-user' },
|
||||
//
|
||||
////////////////////////////////////////////////////
|
||||
{ grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' },
|
||||
{ grant:'DELETE', to:'admin' },
|
||||
];
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=app/bookshop/index.html">
|
||||
</head>
|
||||
@@ -1,3 +0,0 @@
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0;url=app/reviews/index.html">
|
||||
</head>
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "@capire/bookshop-with-reviews",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@capire/bookshop": "*",
|
||||
"@capire/reviews": "*",
|
||||
"@sap/cds": "^5",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"db": {
|
||||
"kind": "sql"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Use enhanced implementation for CatalogService
|
||||
using { CatalogService } from '@capire/bookshop';
|
||||
annotate CatalogService with @impl:'srv/bookshop.js';
|
||||
|
||||
|
||||
// Extend Books with access to Reviews and average ratings
|
||||
using { sap.capire.bookshop.Books } from '@capire/bookshop';
|
||||
using { ReviewsService.Reviews } from '@capire/reviews';
|
||||
extend Books with {
|
||||
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
||||
rating : Decimal;
|
||||
numberOfReviews : Integer;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
const { CatalogService } = require('@capire/bookshop')
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
module.exports = class extends CatalogService {async init(){
|
||||
|
||||
const { Books } = cds.entities('sap.capire.bookshop')
|
||||
|
||||
// Connect to ReviewsService to receive `reviewed` events from it
|
||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||
ReviewsService.on ('reviewed', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
const { subject, count, rating } = msg.data
|
||||
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
||||
})
|
||||
|
||||
return super.init()
|
||||
}}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper for serving static content from npm-installed packages
|
||||
const {dirname,resolve} = require('path')
|
||||
const {static} = require('express')
|
||||
cds.once('listening',()=>{
|
||||
cds.app.use ('/app/bookshop', static (dirname (require.resolve('@capire/bookshop'))+'/app/vue'))
|
||||
cds.app.use ('/app/reviews', static (resolve (__dirname, '../../app/vue')))
|
||||
})
|
||||
19
samples.md
19
samples.md
@@ -51,21 +51,28 @@ Each sub directory essentially is an individual npm package arranged in an [all-
|
||||
- As well as managed data, input validations, and authorization
|
||||
|
||||
|
||||
## [@capire/fiori](fiori)
|
||||
## [@capire/bookstore](bookstore)
|
||||
|
||||
- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:
|
||||
- [@capire/bookshop](bookshop)
|
||||
- [@capire/reviews](reviews)
|
||||
- [@capire/orders](orders)
|
||||
- [@capire/common](common)
|
||||
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
|
||||
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
|
||||
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
|
||||
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
|
||||
- Serving SAP Fiori apps locally
|
||||
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well
|
||||
- [The Vue.js app](reviews/app/vue) imported from reviews is served as well
|
||||
- [The Fiori app](orders/app) imported from orders is served as well
|
||||
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
|
||||
|
||||
|
||||
|
||||
## [@capire/fiori](fiori)
|
||||
|
||||
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
|
||||
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
|
||||
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
|
||||
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
|
||||
- Serving SAP Fiori apps locally
|
||||
|
||||
<br>
|
||||
|
||||
# All-in-one Monorepo
|
||||
|
||||
@@ -42,24 +42,132 @@ describe('cds.ql → cqn', () => {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test('from Foo [<key>]', () => {
|
||||
|
||||
expect(cqn = SELECT`from Foo[11]`)
|
||||
.to.eql(SELECT`from Foo[${11}]`)
|
||||
.to.eql(SELECT.from `Foo[11]`)
|
||||
.to.eql(SELECT.from `Foo[${11}]`)
|
||||
.to.eql(SELECT`Foo[11]`)
|
||||
expect.plain(cqn)
|
||||
.to.eql(CQL`SELECT from Foo[11]`)
|
||||
.to.eql(srv.read`Foo[11]`)
|
||||
.to.eql({
|
||||
SELECT: { from: {
|
||||
ref: [{ id: 'Foo', where: [{ val: 11 }] }]
|
||||
}},
|
||||
if (each === 'SELECT')
|
||||
test('SELECT ( Foo )', () => {
|
||||
expect({
|
||||
SELECT: { from: { ref: ['Foo'] } },
|
||||
})
|
||||
.to.eql(CQL`SELECT from Foo`)
|
||||
.to.eql(SELECT(Foo))
|
||||
})
|
||||
|
||||
if (cdr) expect.plain (cqn)
|
||||
if (each === 'SELECT')
|
||||
test('SELECT ( Foo ) .from ( Bar )', () => {
|
||||
|
||||
expect({
|
||||
SELECT: { columns:[{ref:['Foo']}], from: { ref: ['Bar'] } },
|
||||
})
|
||||
.to.eql(CQL`SELECT Foo from Bar`)
|
||||
.to.eql(SELECT `Foo` .from `Bar`)
|
||||
.to.eql(SELECT `Foo` .from('Bar'))
|
||||
.to.eql(SELECT('Foo').from('Bar'))
|
||||
.to.eql(SELECT(['Foo']).from('Bar'))
|
||||
.to.eql(SELECT(['Foo']).from('Bar'))
|
||||
.to.eql(SELECT `Bar` .columns `Foo`)
|
||||
.to.eql(SELECT `Bar` .columns ('Foo'))
|
||||
.to.eql(SELECT `Bar` .columns (['Foo']))
|
||||
.to.eql(SELECT.from `Bar` .columns ('Foo'))
|
||||
.to.eql(SELECT.from `Bar` .columns (['Foo']))
|
||||
|
||||
expect({
|
||||
SELECT: { columns:[
|
||||
{ref:['Foo']},
|
||||
{ref:['Boo']},
|
||||
], from: { ref: ['Bar'] } },
|
||||
})
|
||||
.to.eql(CQL`SELECT Foo, Boo from Bar`)
|
||||
.to.eql(SELECT `Foo, Boo` .from `Bar`)
|
||||
.to.eql(SELECT `Foo, Boo` .from('Bar'))
|
||||
.to.eql(SELECT('Foo','Boo').from('Bar'))
|
||||
.to.eql(SELECT(['Foo','Boo']).from('Bar'))
|
||||
.to.eql(SELECT `Bar` .columns `Foo, Boo`)
|
||||
.to.eql(SELECT `Bar` .columns ('Foo','Boo'))
|
||||
.to.eql(SELECT `Bar` .columns (['Foo','Boo']))
|
||||
.to.eql(SELECT.from `Bar` .columns ('Foo','Boo'))
|
||||
.to.eql(SELECT.from `Bar` .columns (['Foo','Boo']))
|
||||
|
||||
expect({
|
||||
SELECT: { columns:[
|
||||
{ref:['Foo']},
|
||||
{ref:['Boo']},
|
||||
{ref:['Moo']},
|
||||
], from: { ref: ['Bar'] } },
|
||||
})
|
||||
.to.eql(CQL`SELECT Foo, Boo, Moo from Bar`)
|
||||
.to.eql(SELECT `Foo, Boo, Moo` .from `Bar`)
|
||||
.to.eql(SELECT `Foo, Boo, Moo` .from('Bar'))
|
||||
.to.eql(SELECT('Foo','Boo','Moo').from('Bar'))
|
||||
.to.eql(SELECT(['Foo','Boo','Moo']).from('Bar'))
|
||||
.to.eql(SELECT `Bar` .columns `Foo, Boo, Moo`)
|
||||
.to.eql(SELECT `Bar` .columns ('Foo','Boo','Moo'))
|
||||
.to.eql(SELECT `Bar` .columns (['Foo','Boo','Moo']))
|
||||
.to.eql(SELECT.from `Bar` .columns ('Foo','Boo','Moo'))
|
||||
.to.eql(SELECT.from `Bar` .columns (['Foo','Boo','Moo']))
|
||||
|
||||
|
||||
expect({
|
||||
SELECT: { one:true, columns:[{ref:['Foo']}], from: { ref: ['Bar'] } },
|
||||
})
|
||||
// .to.eql(CQL`SELECT one Foo from Bar`)
|
||||
.to.eql(SELECT.one `Foo` .from `Bar`)
|
||||
.to.eql(SELECT.one `Foo` .from('Bar'))
|
||||
.to.eql(SELECT.one('Foo').from('Bar'))
|
||||
.to.eql(SELECT.one(['Foo']).from('Bar'))
|
||||
.to.eql(SELECT.one(['Foo']).from('Bar'))
|
||||
.to.eql(SELECT.one('Bar',['Foo']))
|
||||
.to.eql(SELECT.one `Bar` .columns `Foo`)
|
||||
.to.eql(SELECT.one('Bar').columns('Foo'))
|
||||
.to.eql(SELECT.one('Bar').columns(['Foo']))
|
||||
.to.eql(SELECT.one.from('Bar',['Foo']))
|
||||
.to.eql(SELECT.one.from('Bar').columns('Foo'))
|
||||
.to.eql(SELECT.one.from('Bar').columns(['Foo']))
|
||||
|
||||
expect({
|
||||
SELECT: { one:true, columns:[
|
||||
{ref:['Foo']},
|
||||
{ref:['Boo']},
|
||||
], from: { ref: ['Bar'] } },
|
||||
})
|
||||
// .to.eql(CQL`SELECT Foo, Boo from Bar`)
|
||||
.to.eql(SELECT.one `Foo, Boo` .from `Bar`)
|
||||
.to.eql(SELECT.one `Foo, Boo` .from('Bar'))
|
||||
.to.eql(SELECT.one('Foo','Boo').from('Bar'))
|
||||
.to.eql(SELECT.one(['Foo','Boo']).from('Bar'))
|
||||
.to.eql(SELECT.one('Bar',['Foo','Boo']))
|
||||
.to.eql(SELECT.one `Bar` .columns `Foo, Boo`)
|
||||
.to.eql(SELECT.one('Bar').columns('Foo','Boo'))
|
||||
.to.eql(SELECT.one('Bar').columns(['Foo','Boo']))
|
||||
.to.eql(SELECT.one.from('Bar',['Foo','Boo']))
|
||||
.to.eql(SELECT.one.from('Bar').columns('Foo','Boo'))
|
||||
.to.eql(SELECT.one.from('Bar').columns(['Foo','Boo']))
|
||||
|
||||
expect({
|
||||
SELECT: { one:true, columns:[
|
||||
{ref:['Foo']},
|
||||
{ref:['Boo']},
|
||||
{ref:['Moo']},
|
||||
], from: { ref: ['Bar'] } },
|
||||
})
|
||||
// .to.eql(CQL`SELECT Foo, Boo, Moo from Bar`)
|
||||
.to.eql(SELECT.one `Foo, Boo, Moo` .from `Bar`)
|
||||
.to.eql(SELECT.one `Foo, Boo, Moo` .from('Bar'))
|
||||
.to.eql(SELECT.one('Foo','Boo','Moo').from('Bar'))
|
||||
.to.eql(SELECT.one(['Foo','Boo','Moo']).from('Bar'))
|
||||
.to.eql(SELECT.one('Bar',['Foo','Boo','Moo']))
|
||||
.to.eql(SELECT.one `Bar` .columns `Foo, Boo, Moo`)
|
||||
.to.eql(SELECT.one('Bar').columns('Foo','Boo','Moo'))
|
||||
.to.eql(SELECT.one('Bar').columns(['Foo','Boo','Moo']))
|
||||
.to.eql(SELECT.one.from('Bar',['Foo','Boo','Moo']))
|
||||
.to.eql(SELECT.one.from('Bar').columns('Foo','Boo','Moo'))
|
||||
.to.eql(SELECT.one.from('Bar').columns(['Foo','Boo','Moo']))
|
||||
|
||||
})
|
||||
|
||||
if (each === 'SELECT')
|
||||
test('from ( Foo )', () => {
|
||||
expect({
|
||||
SELECT: { from: {ref: [{ id:'Foo', where: [{val:11}] }] }}
|
||||
})
|
||||
.to.eql(srv.read`Foo[${11}]`)
|
||||
.to.eql(SELECT`Foo[${11}]`)
|
||||
|
||||
@@ -583,7 +691,7 @@ describe('cds.ql → cqn', () => {
|
||||
.to.eql(UPDATE(Books).where(`ID=`, 4711))
|
||||
.to.eql(cqnWhere)
|
||||
|
||||
const cqnKey = (cds.version >= '5.6.0') ?
|
||||
const cqnKey = (cds.version >= '5.6.0') ?
|
||||
{
|
||||
UPDATE: {
|
||||
entity: { ref: [{ id: 'capire.bookshop.Books', where: [{ ref: ['ID'] }, '=', { val: 4711 }] }] }
|
||||
@@ -657,7 +765,7 @@ describe('cds.ql → cqn', () => {
|
||||
expect(DELETE.from(Books).where({ ID: 4711 }))
|
||||
.to.eql(DELETE.from(Books).where(`ID=`, 4711))
|
||||
.to.eql(cqnWhere)
|
||||
const cqnKey = (cds.version >= '5.6.0') ?
|
||||
const cqnKey = (cds.version >= '5.6.0') ?
|
||||
{
|
||||
DELETE: {
|
||||
from: { ref: [{ id: 'capire.bookshop.Books', where: [{ ref: ['ID'] }, '=', { val: 4711 }]}] }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const {expect} = cds.test
|
||||
|
||||
const { parse:cdr } = cds.ql
|
||||
|
||||
// should become cds.compile(...) when cds5 is released
|
||||
const model = cds.compile.to.csn (`
|
||||
entity Categories {
|
||||
@@ -78,9 +76,7 @@ describe('Hierarchical Data', ()=>{
|
||||
{ ID:101, name:'Cat' },
|
||||
{ ID:108, name:'Catweazle' }
|
||||
]
|
||||
return 'skipped as this will be fixed in a newer cds version'
|
||||
if (cdr) expect ( await SELECT.from(Cats) ).to.containSubset (expected)
|
||||
else expect ( await SELECT.from(Cats) ).to.eql (expected)
|
||||
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user