Compare commits
50 Commits
audit-log-
...
trying-esm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
518429e2a0 | ||
|
|
02000f4a94 | ||
|
|
8f5c33f4f5 | ||
|
|
a893184736 | ||
|
|
9370d0544e | ||
|
|
ee4dcf116c | ||
|
|
58cef5558d | ||
|
|
7753cda65d | ||
|
|
df57a9d8d0 | ||
|
|
2d92b851f1 | ||
|
|
e157fc8a92 | ||
|
|
3b69a80975 | ||
|
|
b80ba7445d | ||
|
|
b733643f2a | ||
|
|
bf317bc2c9 | ||
|
|
bbebff4066 | ||
|
|
eb75394044 | ||
|
|
b8f65b687f | ||
|
|
c49c9691bd | ||
|
|
7e4bc0985e | ||
|
|
912271338d | ||
|
|
bbf1194a09 | ||
|
|
529c431518 | ||
|
|
3f85676edd | ||
|
|
959c07cee3 | ||
|
|
8cc09fde38 | ||
|
|
bb55c432c9 | ||
|
|
5fc86d45ad | ||
|
|
e86d0dc3a2 | ||
|
|
a661fb2d45 | ||
|
|
bfe0c831d6 | ||
|
|
cad615a662 | ||
|
|
7101c58920 | ||
|
|
07dc1e88b3 | ||
|
|
6f8d74dc9a | ||
|
|
ec57c5ea48 | ||
|
|
c040a47279 | ||
|
|
30bfd70c49 | ||
|
|
6fb9581cf1 | ||
|
|
e87d6cdfc5 | ||
|
|
29ea2bc2da | ||
|
|
12574271ac | ||
|
|
17b5cc1ad2 | ||
|
|
26ca7f54ad | ||
|
|
573e78253d | ||
|
|
a7511ed677 | ||
|
|
f1289b436b | ||
|
|
984ea2133b | ||
|
|
179d301acb | ||
|
|
bbf20b1ca3 |
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /
|
||||||
|
versioning-strategy: increase-if-necessary
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
@@ -16,7 +16,8 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
console.debug ('GET', req.params)
|
console.debug ('GET', req.params)
|
||||||
try {
|
try {
|
||||||
const { tarball } = req.params
|
const { tarball } = req.params
|
||||||
const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
|
const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
|
||||||
|
const [, pkg ] = /^\w+-(.+)/.exec(pkgFull)
|
||||||
fs.lstat(tarball,(err => {
|
fs.lstat(tarball,(err => {
|
||||||
if (err) console.debug (`npm pack ../${pkg}`)
|
if (err) console.debug (`npm pack ../${pkg}`)
|
||||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
||||||
@@ -31,7 +32,7 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
app.use('/-', express.static(__dirname))
|
app.use('/-', express.static(__dirname))
|
||||||
|
|
||||||
app.get('/*', (req,res)=>{
|
app.get('/*', (req,res)=>{
|
||||||
const urlRegex = /^\/(@\w+)\/(\w+)/
|
const urlRegex = /^\/(@[\w-]+)\/(.+)/
|
||||||
const url = decodeURIComponent(req.url)
|
const url = decodeURIComponent(req.url)
|
||||||
console.debug ('GET',url)
|
console.debug ('GET',url)
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ const $ = sel => document.querySelector(sel)
|
|||||||
const GET = (url) => axios.get('/browse'+url)
|
const GET = (url) => axios.get('/browse'+url)
|
||||||
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
|
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
|
||||||
|
|
||||||
const books = new Vue ({
|
const books = Vue.createApp ({
|
||||||
|
|
||||||
el:'#app',
|
data() {
|
||||||
|
return {
|
||||||
data: {
|
|
||||||
list: [],
|
list: [],
|
||||||
book: undefined,
|
book: undefined,
|
||||||
order: { quantity:1, succeeded:'', failed:'' }
|
order: { quantity:1, succeeded:'', failed:'' },
|
||||||
|
user: undefined
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -37,12 +38,30 @@ const books = new Vue ({
|
|||||||
book.stock = res.data.stock
|
book.stock = res.data.stock
|
||||||
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
books.order = { quantity, failed: e.response.data.error.message }
|
books.order = { quantity, failed: e.response.data.error ? e.response.data.error.message : e.response.data }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
async login() {
|
||||||
|
try {
|
||||||
|
const { data:user } = await axios.post('/user/login',{})
|
||||||
|
if (user.id !== 'anonymous') books.user = user
|
||||||
|
} catch (err) { books.user = { id: err.message } }
|
||||||
|
},
|
||||||
|
|
||||||
|
async getUserInfo() {
|
||||||
|
try {
|
||||||
|
const { data:user } = await axios.get('/user/me')
|
||||||
|
if (user.id !== 'anonymous') books.user = user
|
||||||
|
} catch (err) { books.user = { id: err.message } }
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
}).mount("#app")
|
||||||
|
|
||||||
// initially fill list of books
|
books.getUserInfo()
|
||||||
books.fetch()
|
books.fetch() // initially fill list of books
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
// hide user info on request
|
||||||
|
if (event.key === 'u') books.user = undefined
|
||||||
|
})
|
||||||
|
|||||||
@@ -5,19 +5,32 @@
|
|||||||
<title> Capire Books </title>
|
<title> Capire Books </title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
||||||
.rating-stars { color:teal }
|
.rating-stars { color:teal }
|
||||||
.succeeded { color:teal }
|
.succeeded { color:teal }
|
||||||
.failed { color:red }
|
.failed { color:red }
|
||||||
|
.user {text-align: end; color: grey;}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="small-container", style="margin-top: 70px;">
|
<body class="small-container", style="margin-top: 70px;">
|
||||||
<div id='app'>
|
<div id='app'>
|
||||||
|
|
||||||
<h1> {{ document.title }} </h1>
|
<form class="user" @submit.prevent="login">
|
||||||
|
<div v-if="user">
|
||||||
|
<div v-if="user.tenant">Tenant: {{ user.tenant }}</div>
|
||||||
|
<div> User: {{ user.id }}</div>
|
||||||
|
<div>Locale: {{ user.locale }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<input type="submit" value="Login" class="muted-button">
|
||||||
|
<!-- <a href="/user/login()">Login</a> -->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h1> Capire Books </h1>
|
||||||
|
|
||||||
<input type="text" placeholder="Search..." @input="search">
|
<input type="text" placeholder="Search..." @input="search">
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* currencies, if not obtained through @capire/common.
|
* currencies, if not obtained through @capire/common.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = async (db)=>{
|
export default async (db)=>{
|
||||||
|
|
||||||
const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode
|
const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode
|
||||||
if (has_common) return
|
if (has_common) return
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
const { CatalogService } = require('./srv/cat-service')
|
import { CatalogService } from './srv/cat-service.js'
|
||||||
module.exports = { CatalogService }
|
export { CatalogService }
|
||||||
|
|||||||
@@ -2,10 +2,18 @@
|
|||||||
"name": "@capire/bookshop",
|
"name": "@capire/bookshop",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A simple self-contained bookshop service.",
|
"description": "A simple self-contained bookshop service.",
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"app",
|
||||||
|
"srv",
|
||||||
|
"db",
|
||||||
|
"index.cds",
|
||||||
|
"index.js"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "^5.0.4",
|
"@sap/cds": ">=5.9",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": "0.4.1"
|
"passport": ">=0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"genres": "cds serve test/genres.cds",
|
"genres": "cds serve test/genres.cds",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const cds = require('@sap/cds')
|
import cds from '@sap/cds'
|
||||||
|
|
||||||
module.exports = cds.service.impl (function(){
|
export default cds.service.impl (function(){
|
||||||
this.before ('NEW','Authors', genid)
|
this.before ('NEW','Authors', genid)
|
||||||
this.before ('NEW','Books', genid)
|
this.before ('NEW','Books', genid)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const cds = require('@sap/cds')
|
import cds from '@sap/cds'
|
||||||
|
|
||||||
class CatalogService extends cds.ApplicationService { init(){
|
export class CatalogService extends cds.ApplicationService { init(){
|
||||||
|
|
||||||
const { Books } = cds.entities ('sap.capire.bookshop')
|
const { Books } = this.entities ('sap.capire.bookshop')
|
||||||
|
|
||||||
// Reduce stock of ordered books if available stock suffices
|
// Reduce stock of ordered books if available stock suffices
|
||||||
this.on ('submitOrder', async req => {
|
this.on ('submitOrder', async req => {
|
||||||
@@ -24,5 +24,3 @@ class CatalogService extends cds.ApplicationService { init(){
|
|||||||
|
|
||||||
return super.init()
|
return super.init()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
module.exports = { CatalogService }
|
|
||||||
|
|||||||
15
bookshop/srv/user-service.cds
Normal file
15
bookshop/srv/user-service.cds
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Exposes user information
|
||||||
|
*/
|
||||||
|
service UserService {
|
||||||
|
/**
|
||||||
|
* The current user
|
||||||
|
*/
|
||||||
|
@odata.singleton entity me {
|
||||||
|
id : String; // user id
|
||||||
|
locale : String;
|
||||||
|
tenant : String;
|
||||||
|
}
|
||||||
|
|
||||||
|
action login() returns me;
|
||||||
|
}
|
||||||
10
bookshop/srv/user-service.js
Normal file
10
bookshop/srv/user-service.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import cds from '@sap/cds'
|
||||||
|
|
||||||
|
export default class UserService extends cds.Service { init(){
|
||||||
|
this.on('READ', 'me', ({ tenant, user, locale }) => ({ id: user.id, locale, tenant }))
|
||||||
|
this.on('login', (req) => {
|
||||||
|
if (req.user._is_anonymous)
|
||||||
|
req._.res.set('WWW-Authenticate','Basic realm="Users"').sendStatus(401)
|
||||||
|
else return this.read('me')
|
||||||
|
})
|
||||||
|
}}
|
||||||
@@ -16,9 +16,9 @@ GET {{server}}/browse/$metadata
|
|||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Browse Books as any user
|
# Browse Books as any user
|
||||||
GET {{server}}/browse/Books?
|
GET {{server}}/browse/ListOfBooks?
|
||||||
# &$select=title,stock
|
# &$select=title,stock
|
||||||
# &$expand=currency
|
&$expand=genre
|
||||||
# &sap-language=de
|
# &sap-language=de
|
||||||
{{me}}
|
{{me}}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"@capire/orders": "*",
|
"@capire/orders": "*",
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@capire/data-viewer": "*",
|
"@capire/data-viewer": "*",
|
||||||
"@sap/cds": "^5",
|
"@sap/cds": ">=5",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "A generic browser for data",
|
"description": "A generic browser for data",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "^5.0.4"
|
"@sap/cds": ">=5.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"app",
|
"app",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"AdminService": {
|
"AdminService": {
|
||||||
"uri": "/admin/",
|
"uri": "admin/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"i18n": "i18n/i18n.properties",
|
"i18n": "i18n/i18n.properties",
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"AdminService": {
|
"AdminService": {
|
||||||
"uri": "/admin/",
|
"uri": "admin/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"CatalogService": {
|
"CatalogService": {
|
||||||
"uri": "/browse/",
|
"uri": "browse/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||||
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
||||||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
||||||
data-sap-ui-compatVersion="edge"
|
data-sap-ui-compatVersion="edge"
|
||||||
data-sap-ui-theme="sap_fiori_3"
|
data-sap-ui-theme="sap_horizon"
|
||||||
data-sap-ui-frameOptions="allow"
|
data-sap-ui-frameOptions="allow"
|
||||||
></script>
|
></script>
|
||||||
<script>
|
<script>
|
||||||
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookstore": "*",
|
"@capire/bookstore": "*",
|
||||||
"@sap/cds": "^5",
|
"@sap/cds": ">=5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": "^0.4.1"
|
"passport": ">=0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cds run --in-memory?",
|
"start": "cds run --in-memory?",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"strategy": "dummy"
|
"kind": "dummy-auth"
|
||||||
},
|
},
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata",
|
"kind": "odata",
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"build": {
|
|
||||||
"target": "gen",
|
|
||||||
"tasks": [{
|
|
||||||
"for": "hana",
|
|
||||||
"src": "db",
|
|
||||||
"options": {
|
|
||||||
"model": [
|
|
||||||
"db",
|
|
||||||
"srv",
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"for": "node-cf",
|
|
||||||
"src": "srv",
|
|
||||||
"options": {
|
|
||||||
"model": [
|
|
||||||
"db",
|
|
||||||
"srv",
|
|
||||||
"app"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
npm run build
|
|
||||||
cf create-service-push
|
|
||||||
cf bind-service gdpr-srv gdpr-pdm -c .pdm/pdm-binding-config.json
|
|
||||||
cf restage gdpr-srv
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
cf delete gdpr-srv -f
|
|
||||||
cf delete gdpr-db-deployer -f
|
|
||||||
cf delete-service gdpr-pdm -f
|
|
||||||
cf delete-service gdpr-auditlog -f
|
|
||||||
cf delete-service gdpr-uaa -f
|
|
||||||
cf delete-service gdpr-hdi -f
|
|
||||||
cf delete-service gdpr-logs -f
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"fullyQualifiedApplicationName": "capire-gdpr",
|
|
||||||
"fullyQualifiedModuleName": "gdpr-srv",
|
|
||||||
"applicationTitle": "Capire GDPR Sample App",
|
|
||||||
"applicationTitleKey": "Capire GDPR Sample App",
|
|
||||||
"applicationURL": "https://capire-gdpr-srv.cfapps.eu10.hana.ondemand.com",
|
|
||||||
"endPoints": [{
|
|
||||||
"type": "odatav4",
|
|
||||||
"serviceName": "PDMService",
|
|
||||||
"serviceURI": "/pdm",
|
|
||||||
"serviceTitle": "Capire GDPR Sample App PDM Service",
|
|
||||||
"serviceTitleKey": "Capire GDPR Sample App PDM Service",
|
|
||||||
"hasGdprV4Annotations": true,
|
|
||||||
"cacheControl": "no-cache"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"xs-security": {
|
|
||||||
"xsappname": "capire-gdpr",
|
|
||||||
"authorities": ["$ACCEPT_GRANTED_AUTHORITIES"]
|
|
||||||
},
|
|
||||||
"fullyQualifiedApplicationName": "capire-gdpr",
|
|
||||||
"appConsentServiceEnabled": true
|
|
||||||
}
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Note: this is designed for the GDPRService being co-located with
|
|
||||||
// orders. It does not work if GDPRService is run as a separate
|
|
||||||
// process, and is not intended to do so.
|
|
||||||
//
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
using {GDPRService} from '../srv/gdpr-service';
|
|
||||||
|
|
||||||
annotate cds.UUID with @Core.Computed;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Orders
|
|
||||||
*/
|
|
||||||
@odata.draft.enabled
|
|
||||||
annotate GDPRService.Orders with @(UI : {
|
|
||||||
SelectionFields : [
|
|
||||||
createdAt,
|
|
||||||
createdBy
|
|
||||||
],
|
|
||||||
LineItem : [
|
|
||||||
{
|
|
||||||
Value : OrderNo,
|
|
||||||
Label : 'Order number'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : customer.firstName,
|
|
||||||
Label : 'First Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : customer.lastName,
|
|
||||||
Label : 'Last Name'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
HeaderInfo : {
|
|
||||||
TypeName : 'Order',
|
|
||||||
TypeNamePlural : 'Orders',
|
|
||||||
Title : {
|
|
||||||
Value : OrderNo,
|
|
||||||
Label : 'Order number'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Identification : [
|
|
||||||
{
|
|
||||||
Value : createdBy,
|
|
||||||
Label : 'Created by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : createdAt,
|
|
||||||
Label : 'Created at'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
HeaderFacets : [
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Created}',
|
|
||||||
Target : '@UI.FieldGroup#Created'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Modified}',
|
|
||||||
Target : '@UI.FieldGroup#Modified'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Facets : [
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Details}',
|
|
||||||
Target : '@UI.FieldGroup#Details'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>OrderItems}',
|
|
||||||
Target : 'Items/@UI.LineItem'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
FieldGroup #Details : {Data : [
|
|
||||||
{
|
|
||||||
Value : customer_ID,
|
|
||||||
Label : 'Customer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : customer.firstName,
|
|
||||||
Label : 'First Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : customer.lastName,
|
|
||||||
Label : 'Last Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : currency_code,
|
|
||||||
Label : 'Currency'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
FieldGroup #Created : {Data : [
|
|
||||||
{
|
|
||||||
Value : createdBy,
|
|
||||||
Label : 'Created by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : createdAt,
|
|
||||||
Label : 'Created at'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
FieldGroup #Modified : {Data : [
|
|
||||||
{
|
|
||||||
Value : modifiedBy,
|
|
||||||
Label : 'Modified by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : modifiedAt,
|
|
||||||
Label : 'Modified at'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
}, ) {
|
|
||||||
createdAt @UI.HiddenFilter : false;
|
|
||||||
createdBy @UI.HiddenFilter : false;
|
|
||||||
customer @ValueList.entity : 'Customers';
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Order Items are not really maintainable in Fiori preview app
|
|
||||||
*/
|
|
||||||
annotate GDPRService.Orders.Items with @(UI : {
|
|
||||||
LineItem : [
|
|
||||||
{
|
|
||||||
Value : product_ID,
|
|
||||||
Label : 'Product ID'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : title,
|
|
||||||
Label : 'Product Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : price,
|
|
||||||
Label : 'Price'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : quantity,
|
|
||||||
Label : 'Quantity'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Identification : [
|
|
||||||
{
|
|
||||||
Value : product_ID,
|
|
||||||
Label : 'Product ID'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : title,
|
|
||||||
Label : 'Product Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : quantity,
|
|
||||||
Label : 'Quantity'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : price,
|
|
||||||
Label : 'Price'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Facets : [{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : 'Order Items',
|
|
||||||
Target : '@UI.Identification'
|
|
||||||
}, ],
|
|
||||||
}, ) {
|
|
||||||
ID @Core.Computed @UI.Hidden : true;
|
|
||||||
title @Core.Computed;
|
|
||||||
price @Core.Computed;
|
|
||||||
quantity @(Common.FieldControl : #Mandatory);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Customers
|
|
||||||
*/
|
|
||||||
@odata.draft.enabled
|
|
||||||
annotate GDPRService.Customers with @(UI : {
|
|
||||||
SelectionFields : [
|
|
||||||
firstName,
|
|
||||||
lastName
|
|
||||||
],
|
|
||||||
LineItem : [
|
|
||||||
{
|
|
||||||
Value : firstName,
|
|
||||||
Label : 'First Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : lastName,
|
|
||||||
Label : 'Last Name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : dateOfBirth,
|
|
||||||
Label : 'Date of Birth'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
HeaderInfo : {
|
|
||||||
TypeName : 'Customer',
|
|
||||||
TypeNamePlural : 'Customers',
|
|
||||||
Title : {
|
|
||||||
Value : lastName,
|
|
||||||
Label : 'Last Name'
|
|
||||||
},
|
|
||||||
Description : {
|
|
||||||
Value : firstName,
|
|
||||||
Label : 'First Name'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Identification : [
|
|
||||||
{
|
|
||||||
Value : createdBy,
|
|
||||||
Label : 'Created by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : createdAt,
|
|
||||||
Label : 'Created at'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
HeaderFacets : [
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Created}',
|
|
||||||
Target : '@UI.FieldGroup#Created'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Modified}',
|
|
||||||
Target : '@UI.FieldGroup#Modified'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
Facets : [
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Details}',
|
|
||||||
Target : '@UI.FieldGroup#Details'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : '{i18n>Addresses}',
|
|
||||||
Target : 'addresses/@UI.LineItem'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
FieldGroup #Details : {Data : [
|
|
||||||
{
|
|
||||||
Value : dateOfBirth,
|
|
||||||
Label : 'Date of Birth'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : email,
|
|
||||||
Label : 'E-Mail'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : creditCardNo,
|
|
||||||
Label : 'Credit Card Number'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
FieldGroup #Created : {Data : [
|
|
||||||
{
|
|
||||||
Value : createdBy,
|
|
||||||
Label : 'Created by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : createdAt,
|
|
||||||
Label : 'Created at'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
FieldGroup #Modified : {Data : [
|
|
||||||
{
|
|
||||||
Value : modifiedBy,
|
|
||||||
Label : 'Modified by'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : modifiedAt,
|
|
||||||
Label : 'Modified at'
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
}, ) {
|
|
||||||
createdAt @UI.HiddenFilter : false;
|
|
||||||
createdBy @UI.HiddenFilter : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
annotate GDPRService.CustomerPostalAddresses with @(UI : {
|
|
||||||
LineItem : [
|
|
||||||
{
|
|
||||||
Value : town,
|
|
||||||
Label : 'Town'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : street,
|
|
||||||
Label : 'Street'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : country.name,
|
|
||||||
Label : 'Country'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
Identification : [
|
|
||||||
{
|
|
||||||
Value : town,
|
|
||||||
Label : 'Town'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : street,
|
|
||||||
Label : 'Street'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value : country_code,
|
|
||||||
Label : 'Country Code'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
Facets : [{
|
|
||||||
$Type : 'UI.ReferenceFacet',
|
|
||||||
Label : 'Customer Postal Address',
|
|
||||||
Target : '@UI.Identification'
|
|
||||||
}, ],
|
|
||||||
}, );
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using {sap.capire.orders} from '@capire/orders';
|
|
||||||
using {sap.capire.gdpr} from './schema';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* annotations for Data Privacy (Personal Data Manager and Audit Logging)
|
|
||||||
*/
|
|
||||||
annotate gdpr.Customers with @PersonalData : {
|
|
||||||
DataSubjectRole : 'Customer',
|
|
||||||
EntitySemantics : 'DataSubject'
|
|
||||||
}{
|
|
||||||
ID @PersonalData.FieldSemantics : 'DataSubjectID';
|
|
||||||
email @PersonalData.IsPotentiallyPersonal;
|
|
||||||
firstName @PersonalData.IsPotentiallyPersonal;
|
|
||||||
lastName @PersonalData.IsPotentiallyPersonal;
|
|
||||||
creditCardNo @PersonalData.IsPotentiallySensitive;
|
|
||||||
dateOfBirth @PersonalData.IsPotentiallyPersonal;
|
|
||||||
}
|
|
||||||
|
|
||||||
annotate gdpr.CustomerPostalAddresses with @PersonalData : {
|
|
||||||
DataSubjectRole : 'Customer',
|
|
||||||
EntitySemantics : 'DataSubjectDetails'
|
|
||||||
}{
|
|
||||||
customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
|
||||||
street @PersonalData.IsPotentiallyPersonal;
|
|
||||||
town @PersonalData.IsPotentiallyPersonal;
|
|
||||||
country @PersonalData.IsPotentiallyPersonal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Personal Data Manager doesn't know EntitySemantics: 'Other' and FieldSemantics: 'ContractRelatedID'
|
|
||||||
* see: https://help.sap.com/viewer/620a3ea6aaf64610accdd05cca9e3de2/Cloud/en-US/5a55fae1eb7c496c92c56071186d76b3.html
|
|
||||||
*/
|
|
||||||
annotate orders.Orders with @PersonalData : {
|
|
||||||
DataSubjectRole : 'Customer',
|
|
||||||
EntitySemantics : 'LegalGround'
|
|
||||||
}{
|
|
||||||
ID @PersonalData.FieldSemantics : 'LegalGroundID';
|
|
||||||
customer @PersonalData.FieldSemantics : 'DataSubjectID';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* additional annotations for Audit Logging
|
|
||||||
*/
|
|
||||||
annotate gdpr.Customers with @AuditLog.Operation : {
|
|
||||||
Read : true,
|
|
||||||
Insert : true,
|
|
||||||
Update : true,
|
|
||||||
Delete : true
|
|
||||||
};
|
|
||||||
|
|
||||||
annotate gdpr.CustomerPostalAddresses with @AuditLog.Operation : {
|
|
||||||
Read : true,
|
|
||||||
Insert : true,
|
|
||||||
Update : true,
|
|
||||||
Delete : true
|
|
||||||
};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ID;modifiedAt;createdAt;createdBy;modifiedBy;customer_ID;street;town;country_code
|
|
||||||
1e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-04-04;2019-01-31;admin@business.com;admin@business.com;8e2f2640-6866-4dcf-8f4d-3027aa831cad;Hauptstrasse 11;Berlin;DE
|
|
||||||
24e718c9-ff99-47f1-8ca3-950c850777d4;2019-04-04;2019-01-30;admin@business.com;admin@business.com;74e718c9-ff99-47f1-8ca3-950c850777d4;Main Street 22;London;GB
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
ID;modifiedAt;createdAt;createdBy;modifiedBy;email;firstName;lastName;creditCardNo;dateOfBirth
|
|
||||||
8e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-04-04;2019-01-31;admin@business.com;admin@business.com;john.doe@test.com;John;Doe;9977-6655-4433-2211;1970-01-01
|
|
||||||
74e718c9-ff99-47f1-8ca3-950c850777d4;2019-04-04;2019-01-30;admin@business.com;admin@business.com;jane.doe@sap.com;Jane;Doe;2211-3344-5566-7788;1980-11-11
|
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
ID;up__ID;quantity;product_ID;title;price
|
|
||||||
4bd2c9df-c19f-47b8-a921-3cde0d863b52;29f15ef6-4a13-47d4-aef4-329a403b49eb;1;201;Wuthering Heights;11.11
|
|
||||||
6c42a40d-5f7c-4c2f-816b-a73c7c28d722;29f15ef6-4a13-47d4-aef4-329a403b49eb;1;271;Catweazle;15
|
|
||||||
748555fc-2cb0-42b5-a361-dd19a50bd682;31c2bd15-5146-4418-b574-866a08911de7;2;252;Eleonora;28
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
ID;createdAt;createdBy;customer_ID;OrderNo;currency_code
|
|
||||||
29f15ef6-4a13-47d4-aef4-329a403b49eb;2019-01-31;john.doe@test.com;8e2f2640-6866-4dcf-8f4d-3027aa831cad;1;EUR
|
|
||||||
31c2bd15-5146-4418-b574-866a08911de7;2019-01-30;jane.doe@test.com;74e718c9-ff99-47f1-8ca3-950c850777d4;2;EUR
|
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
using {
|
|
||||||
Country,
|
|
||||||
managed,
|
|
||||||
cuid
|
|
||||||
} from '@sap/cds/common';
|
|
||||||
using {sap.capire.orders} from '@capire/orders';
|
|
||||||
|
|
||||||
namespace sap.capire.gdpr;
|
|
||||||
|
|
||||||
extend orders.Orders with {
|
|
||||||
customer : Association to Customers;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity Customers : cuid, managed {
|
|
||||||
email : String;
|
|
||||||
firstName : String;
|
|
||||||
lastName : String;
|
|
||||||
creditCardNo : String;
|
|
||||||
dateOfBirth : Date;
|
|
||||||
addresses : Composition of many CustomerPostalAddresses
|
|
||||||
on addresses.customer = $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity CustomerPostalAddresses : cuid, managed {
|
|
||||||
customer : Association to Customers;
|
|
||||||
street : String(128);
|
|
||||||
town : String(128);
|
|
||||||
@assert.integrity : false
|
|
||||||
country : Country;
|
|
||||||
};
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
{
|
|
||||||
"file_suffixes": {
|
|
||||||
"csv": {
|
|
||||||
"plugin_name": "com.sap.hana.di.tabledata.source"
|
|
||||||
},
|
|
||||||
"hdbafllangprocedure": {
|
|
||||||
"plugin_name": "com.sap.hana.di.afllangprocedure"
|
|
||||||
},
|
|
||||||
"hdbanalyticprivilege": {
|
|
||||||
"plugin_name": "com.sap.hana.di.analyticprivilege"
|
|
||||||
},
|
|
||||||
"hdbcalculationview": {
|
|
||||||
"plugin_name": "com.sap.hana.di.calculationview"
|
|
||||||
},
|
|
||||||
"hdbcollection": {
|
|
||||||
"plugin_name": "com.sap.hana.di.collection"
|
|
||||||
},
|
|
||||||
"hdbconstraint": {
|
|
||||||
"plugin_name": "com.sap.hana.di.constraint"
|
|
||||||
},
|
|
||||||
"hdbdropcreatetable": {
|
|
||||||
"plugin_name": "com.sap.hana.di.dropcreatetable"
|
|
||||||
},
|
|
||||||
"hdbflowgraph": {
|
|
||||||
"plugin_name": "com.sap.hana.di.flowgraph"
|
|
||||||
},
|
|
||||||
"hdbfunction": {
|
|
||||||
"plugin_name": "com.sap.hana.di.function"
|
|
||||||
},
|
|
||||||
"hdbgraphworkspace": {
|
|
||||||
"plugin_name": "com.sap.hana.di.graphworkspace"
|
|
||||||
},
|
|
||||||
"hdbhadoopmrjob": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualfunctionpackage.hadoop"
|
|
||||||
},
|
|
||||||
"hdbindex": {
|
|
||||||
"plugin_name": "com.sap.hana.di.index"
|
|
||||||
},
|
|
||||||
"hdblibrary": {
|
|
||||||
"plugin_name": "com.sap.hana.di.library"
|
|
||||||
},
|
|
||||||
"hdbmigrationtable": {
|
|
||||||
"plugin_name": "com.sap.hana.di.table.migration"
|
|
||||||
},
|
|
||||||
"hdbprocedure": {
|
|
||||||
"plugin_name": "com.sap.hana.di.procedure"
|
|
||||||
},
|
|
||||||
"hdbprojectionview": {
|
|
||||||
"plugin_name": "com.sap.hana.di.projectionview"
|
|
||||||
},
|
|
||||||
"hdbprojectionviewconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.projectionview.config"
|
|
||||||
},
|
|
||||||
"hdbreptask": {
|
|
||||||
"plugin_name": "com.sap.hana.di.reptask"
|
|
||||||
},
|
|
||||||
"hdbresultcache": {
|
|
||||||
"plugin_name": "com.sap.hana.di.resultcache"
|
|
||||||
},
|
|
||||||
"hdbrole": {
|
|
||||||
"plugin_name": "com.sap.hana.di.role"
|
|
||||||
},
|
|
||||||
"hdbroleconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.role.config"
|
|
||||||
},
|
|
||||||
"hdbsearchruleset": {
|
|
||||||
"plugin_name": "com.sap.hana.di.searchruleset"
|
|
||||||
},
|
|
||||||
"hdbsequence": {
|
|
||||||
"plugin_name": "com.sap.hana.di.sequence"
|
|
||||||
},
|
|
||||||
"hdbstatistics": {
|
|
||||||
"plugin_name": "com.sap.hana.di.statistics"
|
|
||||||
},
|
|
||||||
"hdbstructuredprivilege": {
|
|
||||||
"plugin_name": "com.sap.hana.di.structuredprivilege"
|
|
||||||
},
|
|
||||||
"hdbsynonym": {
|
|
||||||
"plugin_name": "com.sap.hana.di.synonym"
|
|
||||||
},
|
|
||||||
"hdbsynonymconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.synonym.config"
|
|
||||||
},
|
|
||||||
"hdbsystemversioning": {
|
|
||||||
"plugin_name": "com.sap.hana.di.systemversioning"
|
|
||||||
},
|
|
||||||
"hdbtable": {
|
|
||||||
"plugin_name": "com.sap.hana.di.table"
|
|
||||||
},
|
|
||||||
"hdbtabledata": {
|
|
||||||
"plugin_name": "com.sap.hana.di.tabledata"
|
|
||||||
},
|
|
||||||
"hdbtabletype": {
|
|
||||||
"plugin_name": "com.sap.hana.di.tabletype"
|
|
||||||
},
|
|
||||||
"hdbtrigger": {
|
|
||||||
"plugin_name": "com.sap.hana.di.trigger"
|
|
||||||
},
|
|
||||||
"hdbview": {
|
|
||||||
"plugin_name": "com.sap.hana.di.view"
|
|
||||||
},
|
|
||||||
"hdbvirtualfunction": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualfunction"
|
|
||||||
},
|
|
||||||
"hdbvirtualfunctionconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualfunction.config"
|
|
||||||
},
|
|
||||||
"hdbvirtualpackagehadoop": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualpackage.hadoop"
|
|
||||||
},
|
|
||||||
"hdbvirtualpackagesparksql": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualpackage.sparksql"
|
|
||||||
},
|
|
||||||
"hdbvirtualprocedure": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualprocedure"
|
|
||||||
},
|
|
||||||
"hdbvirtualprocedureconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualprocedure.config"
|
|
||||||
},
|
|
||||||
"hdbvirtualtable": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualtable"
|
|
||||||
},
|
|
||||||
"hdbvirtualtableconfig": {
|
|
||||||
"plugin_name": "com.sap.hana.di.virtualtable.config"
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
|
||||||
},
|
|
||||||
"txt": {
|
|
||||||
"plugin_name": "com.sap.hana.di.copyonly"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
applications:
|
|
||||||
# -----------------------------------------------------------------------------------
|
|
||||||
# HANA Database Content Deployer App
|
|
||||||
# -----------------------------------------------------------------------------------
|
|
||||||
- name: gdpr-db-deployer
|
|
||||||
path: gen/db
|
|
||||||
no-route: true
|
|
||||||
health-check-type: process
|
|
||||||
memory: 256M
|
|
||||||
buildpack: nodejs_buildpack
|
|
||||||
services:
|
|
||||||
- gdpr-logs
|
|
||||||
- gdpr-hdi
|
|
||||||
# -----------------------------------------------------------------------------------
|
|
||||||
# Backend Service
|
|
||||||
# -----------------------------------------------------------------------------------
|
|
||||||
- name: gdpr-srv
|
|
||||||
path: gen/srv
|
|
||||||
memory: 256M
|
|
||||||
buildpack: nodejs_buildpack
|
|
||||||
routes:
|
|
||||||
- route: capire-gdpr-srv.cfapps.eu10.hana.ondemand.com
|
|
||||||
services:
|
|
||||||
- gdpr-logs
|
|
||||||
- gdpr-hdi
|
|
||||||
- gdpr-uaa
|
|
||||||
- gdpr-auditlog
|
|
||||||
# binding with parameters not yet supported -> binding done manually in .etc/deploy.sh
|
|
||||||
#- name: gdpr-pdm
|
|
||||||
# parameters: ./pdm-binding-config.json
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/gdpr",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"dependencies": {
|
|
||||||
"@capire/orders": "../orders",
|
|
||||||
"@sap/audit-logging": "^5.1.0",
|
|
||||||
"@sap/cds": "^5.9",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"hdb": "^0.19.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "rm -rf gen && cds build --production",
|
|
||||||
"deploy": "sh .etc/deploy.sh",
|
|
||||||
"undeploy": "sh .etc/undeploy.sh",
|
|
||||||
"start": "cds run"
|
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"auth": {
|
|
||||||
"__comment__": "workaround to avoid approuter et al. setup",
|
|
||||||
"impl": "srv/auth.js"
|
|
||||||
},
|
|
||||||
"audit-log": {
|
|
||||||
"[development]": {
|
|
||||||
"kind": "audit-log-to-console"
|
|
||||||
},
|
|
||||||
"[production]": {
|
|
||||||
"kind": "audit-log-service"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"kind": "sql"
|
|
||||||
},
|
|
||||||
"uaa": {
|
|
||||||
"kind": "xsuaa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"audit_personal_data": true,
|
|
||||||
"fiori_preview": true,
|
|
||||||
"[production]": {
|
|
||||||
"kibana_formatter": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hana": {
|
|
||||||
"deploy-format": "hdbtable"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# how-to
|
|
||||||
|
|
||||||
## required services and subscriptions
|
|
||||||
|
|
||||||
services:
|
|
||||||
- Audit Log Service
|
|
||||||
- SAP HANA Cloud
|
|
||||||
- SAP HANA Schemas & HDI Containers
|
|
||||||
- Application Logging Service
|
|
||||||
- Personal Data Manager
|
|
||||||
- Authorization and Trust Management Service
|
|
||||||
|
|
||||||
subscriptions:
|
|
||||||
- Audit Log Viewer Service
|
|
||||||
- Personal Data Manager
|
|
||||||
|
|
||||||
## deploy
|
|
||||||
|
|
||||||
after adding the necessary entitlements, do:
|
|
||||||
- `cf l` to log into the respective account
|
|
||||||
- `cd gdpr` (if still in root of `cloud-cap-samples`)
|
|
||||||
- `npm run deploy`, which executes build and deployment via `.etc/deploy.sh`
|
|
||||||
|
|
||||||
## authorization
|
|
||||||
|
|
||||||
create roles for Audit Log Viewer Service and Personal Data Manager, and assign the roles to the respective users
|
|
||||||
|
|
||||||
# open issues
|
|
||||||
|
|
||||||
- deploy via mta, which can bind with parameters, and get rid of scripts in `.etc`
|
|
||||||
- use approuter to remove hacky custom auth impl (`srv/auth.js`)
|
|
||||||
- clarify annotation `EntitySemantics`, which differs between audit logging (`Other`) and personal data manager (`LegalGround`)
|
|
||||||
- annotations for order items Fiori preview app
|
|
||||||
+ `Products` has `@cds.persistence.skip:'always'`
|
|
||||||
- how to reuse intial data from `common`?
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
create-services:
|
|
||||||
- name: gdpr-logs # > for kibana
|
|
||||||
broker: application-logs
|
|
||||||
plan: standard
|
|
||||||
- name: gdpr-hdi # > hana
|
|
||||||
broker: hana
|
|
||||||
plan: hdi-shared
|
|
||||||
- name: gdpr-auditlog # > audit log sink
|
|
||||||
broker: auditlog
|
|
||||||
plan: standard
|
|
||||||
# gdpr-pdm needs to exist before creating gdpr-uaa for authorization grant
|
|
||||||
- name: gdpr-pdm # > personal data manager
|
|
||||||
broker: personal-data-manager-service
|
|
||||||
plan: standard
|
|
||||||
parameters: ./.pdm/pdm-instance-config.json
|
|
||||||
- name: gdpr-uaa # > uaa for authentication
|
|
||||||
broker: xsuaa
|
|
||||||
plan: application
|
|
||||||
parameters: xs-security.json
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* workaround to avoid approuter et al. setup
|
|
||||||
*/
|
|
||||||
|
|
||||||
const jwt = require('jsonwebtoken')
|
|
||||||
const tenant = process.env.VCAP_SERVICES
|
|
||||||
? JSON.parse(process.env.VCAP_SERVICES).xsuaa[0].credentials.tenantid
|
|
||||||
: 'anonymous'
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
/*
|
|
||||||
* decode JWT coming from Personal Data Manager
|
|
||||||
*
|
|
||||||
* DO NOT USE FOR PRODUCTION!
|
|
||||||
* - no token validation
|
|
||||||
* - no xsappname check
|
|
||||||
*/
|
|
||||||
const bearer = req.headers.authorization && req.headers.authorization.split('Bearer ')[1]
|
|
||||||
if (bearer) {
|
|
||||||
const { client_id: id, zid: tenant, scope: roles } = jwt.decode(bearer)
|
|
||||||
req.user = {
|
|
||||||
id,
|
|
||||||
tenant,
|
|
||||||
is: role => roles.some(r => r.endsWith(`.${role}`))
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// mock user that has every role EXCEPT PersonalDataManagerUser
|
|
||||||
const basic = req.headers.authorization && req.headers.authorization.split('Basic ')[1]
|
|
||||||
if (basic) {
|
|
||||||
const [id] = Buffer.from(basic, 'base64').toString('utf-8').split(':')
|
|
||||||
req.user = {
|
|
||||||
id,
|
|
||||||
tenant,
|
|
||||||
is: role => role !== 'PersonalDataManagerUser'
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// no bearer & no basic -> 401
|
|
||||||
res.set('WWW-Authenticate', 'Basic realm="Users"').status(401).end()
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using {
|
|
||||||
sap.capire.orders,
|
|
||||||
sap.capire.gdpr
|
|
||||||
} from '../db/schema';
|
|
||||||
|
|
||||||
@requires : 'admin' // > authorization check
|
|
||||||
service GDPRService {
|
|
||||||
entity Customers as projection on gdpr.Customers;
|
|
||||||
entity Orders as projection on orders.Orders;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using {
|
|
||||||
sap.capire.gdpr as gdpr,
|
|
||||||
sap.capire.orders as orders
|
|
||||||
} from '../db/data-privacy';
|
|
||||||
|
|
||||||
@requires : 'PersonalDataManagerUser' // > authorization check
|
|
||||||
service PDMService {
|
|
||||||
|
|
||||||
entity Customers as projection on gdpr.Customers;
|
|
||||||
entity CustomerPostalAddresses as projection on gdpr.CustomerPostalAddresses;
|
|
||||||
entity Orders as projection on orders.Orders;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* additional annotations for Personal Data Manager's Search Fields
|
|
||||||
*/
|
|
||||||
annotate Customers with @(Communication.Contact : {
|
|
||||||
n : {
|
|
||||||
surname : lastName,
|
|
||||||
given : firstName
|
|
||||||
},
|
|
||||||
bday : dateOfBirth
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
const cds = require('@sap/cds')
|
|
||||||
|
|
||||||
/*
|
|
||||||
* in development, write audit logs to custom sink (i.e., to console in this example)
|
|
||||||
*/
|
|
||||||
cds.on('served', async () => {
|
|
||||||
if (process.env.NODE_ENV === 'production') return
|
|
||||||
|
|
||||||
const auditLogService = await cds.connect.to('audit-log')
|
|
||||||
// use prepend to get called before the generic implementation
|
|
||||||
auditLogService.prepend(function() {
|
|
||||||
const LOG = cds.log('my custom audit logging impl')
|
|
||||||
// triggered when reading sensitive personal data
|
|
||||||
this.on('dataAccessLog', function(req) {
|
|
||||||
const { accesses } = req.data
|
|
||||||
for (const access of accesses) LOG.info(access)
|
|
||||||
})
|
|
||||||
// triggered when modifying personal data
|
|
||||||
this.on('dataModificationLog', function(req) {
|
|
||||||
const { modifications } = req.data
|
|
||||||
for (const modification of modifications) LOG.info(modification)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = cds.server
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"xsappname": "capire-gdpr",
|
|
||||||
"tenant-mode": "shared",
|
|
||||||
"scopes": [{
|
|
||||||
"name": "$XSAPPNAME.PersonalDataManagerUser",
|
|
||||||
"description": "Authority for Personal Data Manager",
|
|
||||||
"grant-as-authority-to-apps": [
|
|
||||||
"$XSSERVICENAME(gdpr-pdm)"
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
"name": "$XSAPPNAME.admin",
|
|
||||||
"description": "Administrator"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
"start:ts": "cds-ts serve srv/world.cds"
|
"start:ts": "cds-ts serve srv/world.cds"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "^5.0.4"
|
"@sap/cds": ">=5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "*",
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "*",
|
||||||
"ts-jest": "^27.0.2",
|
"ts-jest": "^27.0.2",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
|
|
||||||
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||||
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
||||||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
||||||
data-sap-ui-compatVersion="edge"
|
data-sap-ui-compatVersion="edge"
|
||||||
data-sap-ui-theme="sap_fiori_3"
|
data-sap-ui-theme="sap_horizon"
|
||||||
data-sap-ui-frameOptions="allow"
|
data-sap-ui-frameOptions="allow"
|
||||||
></script>
|
></script>
|
||||||
<script>
|
<script>
|
||||||
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@sap/cds": "^5"
|
"@sap/cds": ">=5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5187
package-lock.json
generated
5187
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,25 +10,24 @@
|
|||||||
"@capire/common": "./common",
|
"@capire/common": "./common",
|
||||||
"@capire/data-viewer": "./data-viewer",
|
"@capire/data-viewer": "./data-viewer",
|
||||||
"@capire/fiori": "./fiori",
|
"@capire/fiori": "./fiori",
|
||||||
"@capire/gdpr": "./gdpr",
|
|
||||||
"@capire/hello": "./hello",
|
"@capire/hello": "./hello",
|
||||||
"@capire/media": "./media",
|
"@capire/media": "./media",
|
||||||
"@capire/orders": "./orders",
|
"@capire/orders": "./orders",
|
||||||
"@capire/reviews": "./reviews",
|
"@capire/reviews": "./reviews",
|
||||||
"@sap/cds": "^5.5.3"
|
"@sap/cds": ">=5.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-subset": "^1.6.0",
|
"chai-subset": "^1.6.0",
|
||||||
"sqlite3": "npm:@mendix/sqlite3@^5"
|
"semver": "^7",
|
||||||
|
"sqlite3": "^5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
||||||
"registry": "node .registry/server.js",
|
"registry": "node .registry/server.js",
|
||||||
"bookshop": "cds watch bookshop",
|
"bookshop": "cds watch bookshop",
|
||||||
"fiori": "cds watch fiori",
|
"fiori": "cds watch fiori",
|
||||||
"gdpr": "cds watch gdpr",
|
|
||||||
"hello": "cds watch hello",
|
"hello": "cds watch hello",
|
||||||
"media": "cds watch media",
|
"media": "cds watch media",
|
||||||
"mocha": "npx mocha || echo",
|
"mocha": "npx mocha || echo",
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ const GET = (url) => axios.get('/reviews'+url)
|
|||||||
const PUT = (cmd,data) => axios.patch('/reviews'+cmd,data)
|
const PUT = (cmd,data) => axios.patch('/reviews'+cmd,data)
|
||||||
const POST = (cmd,data) => axios.post('/reviews'+cmd,data)
|
const POST = (cmd,data) => axios.post('/reviews'+cmd,data)
|
||||||
|
|
||||||
const reviews = new Vue ({
|
const reviews = Vue.createApp ({
|
||||||
|
|
||||||
el:'#app',
|
data() {
|
||||||
|
return {
|
||||||
data: {
|
list: [],
|
||||||
list: [],
|
review: undefined,
|
||||||
review: undefined,
|
message: {},
|
||||||
message: {},
|
Ratings: Object.entries({
|
||||||
Ratings: Object.entries({
|
|
||||||
5 : '★★★★★',
|
5 : '★★★★★',
|
||||||
4 : '★★★★',
|
4 : '★★★★',
|
||||||
3 : '★★★',
|
3 : '★★★',
|
||||||
2 : '★★',
|
2 : '★★',
|
||||||
1 : '★',
|
1 : '★',
|
||||||
}).reverse()
|
}).reverse()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -66,7 +66,7 @@ const reviews = new Vue ({
|
|||||||
datetime: (d) => d && new Date(d).toLocaleString(),
|
datetime: (d) => d && new Date(d).toLocaleString(),
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
}).mount("#app")
|
||||||
|
|
||||||
// initially fill list of my reviews
|
// initially fill list of my reviews
|
||||||
reviews.fetch()
|
reviews.fetch()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<title> Capire Reviews </title>
|
<title> Capire Reviews </title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
||||||
.rating-stars { color:teal }
|
.rating-stars { color:teal }
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<body class="small-container", style="margin-top: 70px;">
|
<body class="small-container", style="margin-top: 70px;">
|
||||||
<div id='app'>
|
<div id='app'>
|
||||||
|
|
||||||
<h1> {{ document.title }} </h1>
|
<h1> Capire Reviews </h1>
|
||||||
|
|
||||||
<input type="text" placeholder="Search..." @input="search">
|
<input type="text" placeholder="Search..." @input="search">
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"index.cds"
|
"index.cds"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "^5",
|
"@sap/cds": ">=5",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ describe('cds.ql → cqn', () => {
|
|||||||
.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 `Bar` .columns ('{ Foo, Boo }'))
|
||||||
.to.eql(SELECT `Bar` .columns ('Foo','Boo'))
|
.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'))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { expect } = cds.test ('@capire/bookshop')
|
const { expect } = cds.test ('@capire/bookshop')
|
||||||
|
|
||||||
describe('Consuming Services locally', () => {
|
describe('cap/samples - Consuming Services locally', () => {
|
||||||
//
|
//
|
||||||
it('bootstrapped the database successfully', ()=>{
|
it('bootstrapped the database successfully', ()=>{
|
||||||
const { AdminService } = cds.services
|
const { AdminService } = cds.services
|
||||||
@@ -32,6 +32,27 @@ describe('Consuming Services locally', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).where(`name like`, 'E%')
|
}).where(`name like`, 'E%')
|
||||||
|
if (require('semver').gte(cds.version, '5.9.0')) {
|
||||||
|
expect(authors).to.containSubset([
|
||||||
|
{
|
||||||
|
name: 'Emily Brontë',
|
||||||
|
books: [
|
||||||
|
{
|
||||||
|
title: 'Wuthering Heights',
|
||||||
|
currency: { name: 'British Pound', symbol: '£' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Edgar Allen Poe',
|
||||||
|
books: [
|
||||||
|
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
||||||
|
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
return
|
||||||
|
}
|
||||||
expect(authors).to.containSubset([
|
expect(authors).to.containSubset([
|
||||||
{
|
{
|
||||||
name: 'Emily Brontë',
|
name: 'Emily Brontë',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('Custom Handlers', () => {
|
describe('cap/samples - Custom Handlers', () => {
|
||||||
|
|
||||||
it('should reject out-of-stock orders', async () => {
|
it('should reject out-of-stock orders', async () => {
|
||||||
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { GET, expect } = cds.test (__dirname+'/../hello')
|
const { GET, expect } = cds.test (__dirname+'/../hello')
|
||||||
|
|
||||||
describe('Hello world!', () => {
|
describe('cap/samples - Hello world!', () => {
|
||||||
|
|
||||||
it('should say hello with class impl', async () => {
|
it('should say hello with class impl', async () => {
|
||||||
const {data} = await GET `/say/hello(to='world')`
|
const {data} = await GET `/say/hello(to='world')`
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const model = cds.compile.to.csn (`
|
|||||||
const {Categories:Cats} = model.definitions
|
const {Categories:Cats} = model.definitions
|
||||||
|
|
||||||
|
|
||||||
describe('Hierarchical Data', ()=>{
|
describe('cap/samples - Hierarchical Data', ()=>{
|
||||||
|
|
||||||
before ('bootstrap sqlite in-memory db...', async()=>{
|
before ('bootstrap sqlite in-memory db...', async()=>{
|
||||||
await cds.deploy (model) .to ('sqlite::memory:')
|
await cds.deploy (model) .to ('sqlite::memory:')
|
||||||
@@ -35,6 +35,21 @@ describe('Hierarchical Data', ()=>{
|
|||||||
))
|
))
|
||||||
|
|
||||||
it ('supports nested reads', async()=>{
|
it ('supports nested reads', async()=>{
|
||||||
|
if (require('semver').gte(cds.version, '5.9.0')) {
|
||||||
|
expect (await
|
||||||
|
SELECT.one.from (Cats, c=>{
|
||||||
|
c.ID, c.name.as('parent'), c.children (c=>{
|
||||||
|
c.name.as('child')
|
||||||
|
})
|
||||||
|
}) .where ({name:'Cat'})
|
||||||
|
) .to.eql (
|
||||||
|
{ ID:101, parent:'Cat', children:[
|
||||||
|
{ child:'Kitty' },
|
||||||
|
{ child:'Catwoman' },
|
||||||
|
]}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
expect (await
|
expect (await
|
||||||
SELECT.one.from (Cats, c=>{
|
SELECT.one.from (Cats, c=>{
|
||||||
c.ID, c.name.as('parent'), c.children (c=>{
|
c.ID, c.name.as('parent'), c.children (c=>{
|
||||||
@@ -50,6 +65,25 @@ describe('Hierarchical Data', ()=>{
|
|||||||
})
|
})
|
||||||
|
|
||||||
it ('supports deeply nested reads', async()=>{
|
it ('supports deeply nested reads', async()=>{
|
||||||
|
if (require('semver').gte(cds.version, '5.9.0')) {
|
||||||
|
expect (await SELECT.one.from (Cats, c=>{
|
||||||
|
c.ID, c.name, c.children (
|
||||||
|
c => { c.name },
|
||||||
|
{levels:3}
|
||||||
|
)
|
||||||
|
}) .where ({name:'Cat'})
|
||||||
|
) .to.eql (
|
||||||
|
{ ID:101, name:'Cat', children:[
|
||||||
|
{ name:'Kitty', children:[
|
||||||
|
{ name:'Kitty Cat', children:[
|
||||||
|
{ name:'Aristocat' }, ]}, // level 3
|
||||||
|
{ name:'Kitty Bat', children:[] }, ]},
|
||||||
|
{ name:'Catwoman', children:[
|
||||||
|
{ name:'Catalina', children:[] } ]},
|
||||||
|
]}
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
expect (await SELECT.one.from (Cats, c=>{
|
expect (await SELECT.one.from (Cats, c=>{
|
||||||
c.ID, c.name, c.children (
|
c.ID, c.name, c.children (
|
||||||
c => { c.name },
|
c => { c.name },
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { GET, expect, cds } = require('@sap/cds/lib').test (__dirname)
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('Localized Data', () => {
|
describe('cap/samples - Localized Data', () => {
|
||||||
|
|
||||||
it('serves localized $metadata documents', async () => {
|
it('serves localized $metadata documents', async () => {
|
||||||
const { data } = await GET`/browse/$metadata?sap-language=de`
|
const { data } = await GET`/browse/$metadata?sap-language=de`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const _model = '@capire/reviews'
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('Messaging', ()=>{
|
describe('cap/samples - Messaging', ()=>{
|
||||||
|
|
||||||
it ('should bootstrap sqlite in-memory db', async()=>{
|
it ('should bootstrap sqlite in-memory db', async()=>{
|
||||||
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
||||||
|
|||||||
@@ -1,9 +1,48 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { GET, expect } = cds.test ('@capire/bookshop')
|
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
|
||||||
|
|
||||||
describe('OData Protocol', () => {
|
describe('cap/samples - Bookshop APIs', () => {
|
||||||
|
|
||||||
|
// Genres
|
||||||
|
const Drama = {
|
||||||
|
"name": "Drama",
|
||||||
|
"descr": null,
|
||||||
|
"ID": 11,
|
||||||
|
"parent_ID": 10
|
||||||
|
}
|
||||||
|
const Mystery = {
|
||||||
|
"name": "Mystery",
|
||||||
|
"descr": null,
|
||||||
|
"ID": 16,
|
||||||
|
"parent_ID": 10
|
||||||
|
}
|
||||||
|
const Fantasy = {
|
||||||
|
"name": "Fantasy",
|
||||||
|
"descr": null,
|
||||||
|
"ID": 13,
|
||||||
|
"parent_ID": 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currencies
|
||||||
|
const GBP = {
|
||||||
|
"name": "British Pound",
|
||||||
|
"descr": null,
|
||||||
|
"code": "GBP",
|
||||||
|
"symbol": "£"
|
||||||
|
}
|
||||||
|
const USD = {
|
||||||
|
"name": "US Dollar",
|
||||||
|
"descr": null,
|
||||||
|
"code": "USD",
|
||||||
|
"symbol": "$"
|
||||||
|
}
|
||||||
|
const JPY = {
|
||||||
|
"name": "Yen",
|
||||||
|
"descr": null,
|
||||||
|
"code": "JPY",
|
||||||
|
"symbol": "¥"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
it('serves $metadata documents in v4', async () => {
|
it('serves $metadata documents in v4', async () => {
|
||||||
@@ -17,6 +56,16 @@ describe('OData Protocol', () => {
|
|||||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('serves ListOfBooks?$expand=genre,currency', async () => {
|
||||||
|
const { data } = await GET `/browse/ListOfBooks ${{
|
||||||
|
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
|
||||||
|
}}`
|
||||||
|
expect(data.value).to.eql([
|
||||||
|
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
||||||
|
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('supports $search in multiple fields', async () => {
|
it('supports $search in multiple fields', async () => {
|
||||||
const { data } = await GET `/browse/Books ${{
|
const { data } = await GET `/browse/Books ${{
|
||||||
params: { $search: 'Po', $select: `title,author` },
|
params: { $search: 'Po', $select: `title,author` },
|
||||||
@@ -75,4 +124,16 @@ describe('OData Protocol', () => {
|
|||||||
{ ID: 271, title: 'Catweazle' },
|
{ ID: 271, title: 'Catweazle' },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('serves user info', async () => {
|
||||||
|
{
|
||||||
|
const { data } = await GET (`/user/me`)
|
||||||
|
expect(data).to.containSubset({ id: 'alice', locale:'en', tenant: null })
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
||||||
|
expect(data).to.containSubset({ id: 'joe', locale:'en', tenant: null })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const { resolve } = require('path')
|
|||||||
const verbose = process.env.CDS_TEST_VERBOSE
|
const verbose = process.env.CDS_TEST_VERBOSE
|
||||||
// ||true
|
// ||true
|
||||||
|
|
||||||
describe('Local NPM registry', () => {
|
describe('cap/samples - Local NPM registry', () => {
|
||||||
let registry
|
let registry
|
||||||
let axios
|
let axios
|
||||||
const cwd = resolve(__dirname, '..')
|
const cwd = resolve(__dirname, '..')
|
||||||
@@ -20,7 +20,7 @@ describe('Local NPM registry', () => {
|
|||||||
|
|
||||||
after(() => { registry.kill() })
|
after(() => { registry.kill() })
|
||||||
|
|
||||||
for (const mod of ['bookshop','fiori','orders','reviews']) {
|
for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
|
||||||
it(`should serve ${mod}`, async () => {
|
it(`should serve ${mod}`, async () => {
|
||||||
const resp = await axios.get(`/@capire/${mod}`)
|
const resp = await axios.get(`/@capire/${mod}`)
|
||||||
expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
|
expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
|
||||||
|
|||||||
Reference in New Issue
Block a user