This commit is contained in:
Daniel
2020-03-02 00:08:49 +01:00
parent d9df2930cb
commit 26d7fc767c
71 changed files with 141 additions and 34 deletions

View File

@@ -0,0 +1,28 @@
using { sap } from '@sap/cds/common';
namespace sap.common;
/**
* The Code Lists below are designed as optional extensions to
* the base schema. Switch them on by adding an Association to
* one of the code list entities in your models or by:
* annotate sap.common.Countries with @cds.persistence.skip:false;
*/
extend sap.common.Countries {
regions : Composition of many Regions on regions._parent = $self.code;
}
entity Regions : sap.common.CodeList {
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
children : Composition of many Regions on children._parent = $self.code;
cities : Composition of many Cities on cities.region = $self;
_parent : String(11);
}
entity Cities : sap.common.CodeList {
key code : String(11);
region : Association to Regions;
districts : Composition of many Districts on districts.city = $self;
}
entity Districts : sap.common.CodeList {
key code : String(11);
city : Association to Cities;
}

View File

@@ -0,0 +1,58 @@
namespace sap.capire.contacts;
//--------------------------------------------------------------------------
// Aspects
aspect Organization {
orgname : String(111);
}
aspect Person {
firstname : String(111);
lastname : String(111);
prefix : String(11);
suffix : String(11);
middle : String(11);
dateOfBirth : Date; placeOfBirth : String;
dateOfDeath : Date; placeOfDeath : String;
}
aspect PostalAddress {
street : String(222) @multiline;
postCode : String(11);
district : String(111);
city : String(111);
region : String(111);
country : String(111);
}
aspect ContactOptions {
email : String @JSON:[{ kind:String, address: EmailAddress }];
phone : String @JSON:[{ kind:String, number: PhoneNumber }];
// phone : array of { kind:String; number: PhoneNumber };
// addresses : Composition of many PostalAddress;
}
type EmailAddress : String;
type PhoneNumber : String;
//--------------------------------------------------------------------------
// Entities
@cds.persistence.skip:'if-unused'
entity Contacts : Person, Organization, ContactOptions {
key ID : UUID;
isOrg : Boolean;
addresses : Composition of many PostalAddresses on addresses.contact = $self;
}
@cds.persistence.skip:'if-unused'
entity PostalAddresses : PostalAddress {
contact : Association to Contacts;
kind : String;
key ID : UUID;
}

View File

@@ -0,0 +1,2 @@
using from './db/code-lists';
using from './db/schema';

View File

@@ -0,0 +1,10 @@
{
"name": "@sap/capire-contacts",
"version": "1.0.0",
"description": "A reuse package providing common domain models and services for contacts-related data.",
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
"license": "SAP SAMPLE CODE LICENSE",
"dependencies": {
"@sap/cds": "latest"
}
}

View File

@@ -0,0 +1,67 @@
# Common Contacts Sample
This sample provides a reuse package with common domain models and services for contacts-related data.
## Usage
#### Import to your project
npm install @sap/capire-contacts
> e.g. see: [bookstore](../bookstore/package.json)
#### Reusing aspects
Define own entities derived from the pre-defined aspects as in [_bookstore_](../bookstore/db/schema.cds):
```swift
using { sap.capire.contacts.Person } from '@sap/capire-contacts';
entity Authors : contacts.Person { ... }
```
> **Note:** All entities in this package are annotated with _`@cds.persistence.skip`:'if-unused'_, so they will be ignored if not referred to from other entities in your models.
#### Reusing entities
Reuse the entities as in this example from [_users-service_](../users-service/srv/services.cds):
```swift
using { sap.capire.contacts.Contacts } from '@sap/capire-contacts';
service UsersService @(requires:'authenticated-user') {
entity MyProfile as select from Contacts where ID=$user;
...
}
```
#### Reusing code lists
Reuse the code lists as in [_./tests/index.cds_](./tests/index.cds):
```swift
service Sue { ...
// expose Countries to activate provided code lists
@readonly entity Countries as projection on sap.capire.contacts.Countries;
}
```
#### Reuse code list service
```js
const { intercept } = require ('@sap/capire-contacts/srv/code-lists')
```
## Content
## Concepts
* [Reuse of packages](https://cap.cloud.sap/docs/get-started/projects#reuse)
* Code Lists, `@sap/cds/common` and `@cds.persistence.skip`: 'if-unused'
* Using `aspects` vs `entities`

View File

@@ -0,0 +1,70 @@
const cds = require ('@sap/cds')
const READ='READ', WRITE = ['CREATE','UPDATE']
const intercept = exports.intercept = cds.service.impl (async (srv) => {
for (let each in srv.entities) {
// intercept JSON-encoded elements
const jsons = await jsonsIn (srv.entities[each].elements)
if (jsons) {
srv.before (WRITE, each, ({data:row})=>{
for (let e of jsons) if (row[e]) row[e] = JSON.stringify (row[e])
})
srv.after (READ, each, (row)=>{
for (let e of jsons) if (row[e]) row[e] = JSON.parse (row[e])
})
}
// intercept references
const refs = await refsIn (srv.entities[each].elements, srv.model)
if (refs) srv.after (READ, each, (rows, req)=>{
for (let row of rows) {
for (let {element,codelist} of refs) {
const entry = codelist [row[element]]
if (entry) {
const localized = entry.texts [req.user.locale || intercept.locale]
row[element] = localized ? localized.name : entry.name
}
}
}
})
}
})
function jsonsIn (elements) {
const jsons=[]; for (let e in elements) {
if (elements[e]['@JSON']) jsons.push(e)
}
return jsons.length && jsons
}
async function refsIn (elements, model) {
const refs=[]; for (let e in elements) {
const $ref = elements[e]['@ref']
if ($ref) {
const d = model.definitions [$ref['=']]
refs.push({
element:e,
codelist: CodeLists[d.name] || (CodeLists[d.name] = await load(d))
})
}
}
return refs.length && refs
}
const load = exports.load = async (codelist) => {
const all = {}
const [entries,texts] = await Promise.all ([
SELECT.from (codelist),
SELECT.from (codelist.elements.texts.target)
])
for (let {code,name,descr} of entries) all[code] = {name,descr}
for (let {code,locale,name,descr} of texts) (all[code].texts || (all[code].texts={})) [locale] = {name,descr}
return all
}
const CodeLists = {}

View File

@@ -0,0 +1,68 @@
const {load,intercept} = require ('../srv/code-lists')
const cds = require ('@sap/cds')
// patch-enhance cds.ql
const select = SELECT.from('.').__proto__.__proto__, query = select.__proto__
query.then = function (r,e) { return db.run(this) .then (r,e || ((e)=>{throw e})) }
let db, Countries, Australia = {
name: 'Australia', descr: 'Commonwealth of Australia', texts: {
de: { name: 'Australien', descr: 'Commonwealth Australien' }
}
}
describe ('code list tests', ()=>{
it ('should deploy the db schema to sqlite in-memory', async()=>{
db = await cds.deploy (__dirname) .to ('sqlite::memory:', {silent:true,primary:true})
Countries = db.model.entities ['sap.common.Countries']
expect (Countries) .toBeDefined()
})
it ('should read Countries', async()=>{
const countries = await SELECT ('code','name') .from (Countries)
expect (countries) .toContainEqual ({ code: 'AU', name: 'Australia' })
})
it ('should read Countries_texts', async()=>{
const countries = await SELECT ('locale','code','name') .from ('sap.common.Countries_texts')
expect (countries) .toContainEqual ({ locale: 'de', code: 'AU', name: 'Australien' })
})
it ('should read code lists with translated texts', async()=>{
const {AU} = await load (Countries)
expect (AU) .toEqual (Australia)
})
cds.env.singletenant = true
it ('should serve services with localized data', async()=>{
const { Sue:sue } = await cds.serve (__dirname)
const { Foos } = sue.entities
await sue.create (Foos) .entries ({country:'Avalon'})
await sue.create (Foos) .entries ({country:'AU'})
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'AU' } ])
})
it ('should resolve countries', async()=>{
const sue = await cds.connect.to ('Sue')
await intercept (sue)
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'Australia' } ])
intercept.locale = 'de'
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'Australien' } ])
console.log (await sue.read('Foos'))
})
it ('should read countries with expand to translated texts', async()=>{
const countries = await cds.read (Countries, c=>{
c.name, c.texts (t => {
t.locale, t.name
})
})
console.log (countries)
})
it ('should disconnect from db', ()=> db.disconnect())
//> FIXME: that should not be required!
})

View File

@@ -0,0 +1,11 @@
using { sap } from '..';
entity Foo {
key ID : Integer;
country : String @ref: sap.capire.contacts.Countries;
}
service Sue {
entity Foos as projection on Foo;
// expose Countries to activate provided code lists
@readonly entity Countries as projection on sap.capire.contacts.Countries;
}