Compare commits

..

7 Commits

Author SHA1 Message Date
Daniel Hutzel
7ccd11062a Moved ord-service into plugin package 2025-02-21 18:19:54 +01:00
Daniel Hutzel
20348e0776 . 2025-02-21 14:39:47 +01:00
Daniel Hutzel
6acd5338d9 Adding arbitrary express routes 2025-02-21 14:37:26 +01:00
Daniel Hutzel
413d4de745 some samples how to serve things 2025-02-21 12:57:05 +01:00
Daniel Hutzel
23bea0f629 cosmetics 2025-01-31 14:42:40 +01:00
dependabot[bot]
8c6ba30673 Bump the production-dependencies group across 1 directory with 3 updates (#790)
Bumps the production-dependencies group with 3 updates in the / directory: [@sap/cds](https://cap.cloud.sap/), [express](https://github.com/expressjs/express) and [@cap-js-community/odata-v2-adapter](https://github.com/cap-js-community/odata-v2-adapter).


Updates `@sap/cds` from 8.5.0 to 8.6.1

Updates `express` from 4.21.1 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2)

Updates `@cap-js-community/odata-v2-adapter` from 1.13.8 to 1.14.0
- [Release notes](https://github.com/cap-js-community/odata-v2-adapter/releases)
- [Changelog](https://github.com/cap-js-community/odata-v2-adapter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/cap-js-community/odata-v2-adapter/compare/v1.13.8...v1.14.0)

---
updated-dependencies:
- dependency-name: "@sap/cds"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: express
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@cap-js-community/odata-v2-adapter"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 13:27:56 +00:00
dependabot[bot]
06956b1077 Bump the development-dependencies group across 1 directory with 4 updates (#794)
Bumps the development-dependencies group with 4 updates in the / directory: [@cap-js/cds-types](https://github.com/cap-js/cds-types), [@cap-js/sqlite](https://github.com/cap-js/cds-dbs), [axios](https://github.com/axios/axios) and [eslint](https://github.com/eslint/eslint).


Updates `@cap-js/cds-types` from 0.8.0 to 0.9.0
- [Release notes](https://github.com/cap-js/cds-types/releases)
- [Changelog](https://github.com/cap-js/cds-types/blob/main/CHANGELOG.md)
- [Commits](https://github.com/cap-js/cds-types/compare/v0.8.0...v0.9.0)

Updates `@cap-js/sqlite` from 1.7.7 to 1.7.8
- [Release notes](https://github.com/cap-js/cds-dbs/releases)
- [Changelog](https://github.com/cap-js/cds-dbs/blob/main/release-please-config.json)
- [Commits](https://github.com/cap-js/cds-dbs/compare/sqlite-v1.7.7...sqlite-v1.7.8)

Updates `axios` from 1.7.8 to 1.7.9
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.8...v1.7.9)

Updates `eslint` from 9.16.0 to 9.19.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.16.0...v9.19.0)

---
updated-dependencies:
- dependency-name: "@cap-js/cds-types"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@cap-js/sqlite"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 14:26:02 +01:00
18 changed files with 258 additions and 147 deletions

View File

@@ -19,13 +19,13 @@ const books = Vue.createApp ({
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
async fetch (etc='') {
const {data} = await GET(`/Books?${etc}`)
const {data} = await GET(`/ListOfBooks?$expand=genre($select=name),currency($select=symbol)${etc}`)
books.list = data.value
},
async inspect (eve) {
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Book/${book.ID}?$select=descr,stock,image`)
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data)
books.order = { quantity:1 }
setTimeout (()=> $('form > input').focus(), 111)

View File

@@ -45,11 +45,11 @@
<tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect">
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.genre }}</td>
<td>{{ book.genre.name }}</td>
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }})
</td>
<td>{{ book.currency }} {{ book.price }}</td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
</tr>
</table>

View File

@@ -13,6 +13,7 @@
"@cap-js/sqlite": "*"
},
"dependencies": {
"@cap-js/ord": "2",
"@sap/cds": ">=7",
"express": "^4.17.1"
},

View File

@@ -1,5 +1,5 @@
using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'admin', path:'/admin') {
entity Books as projection on my.Books;
// entity Authors as projection on my.Authors;
entity Authors as projection on my.Authors;
}

View File

@@ -2,20 +2,15 @@ using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
/** For displaying lists of Books */
@readonly entity Books as projection on Book excluding { descr };
@readonly entity ListOfBooks as projection on Books
excluding { descr };
/** For display in details pages */
@readonly entity Book as projection on my.Books { *,
currency.name as currencyName, // flattened
currency.symbol as currency, // flattened
author.name as author, // flattened
genre.name as genre, // flattened
} excluding {
createdBy, modifiedBy, // as end users shouldn't see them
localized, texts, // as end users don't need them
};
@readonly entity Books as projection on my.Books { *,
author.name as author
} excluding { createdBy, modifiedBy };
@requires: 'authenticated-user'
action submitOrder ( book: Book:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Book:ID; quantity: Integer; buyer: String };
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
}

View File

@@ -3,10 +3,10 @@ const cds = require('@sap/cds')
class CatalogService extends cds.ApplicationService { init() {
const { Books } = cds.entities('sap.capire.bookshop')
const { Books:Book } = this.entities
const { ListOfBooks } = this.entities
// Add some discount for overstocked books
this.after('each', Book, book => {
this.after('each', ListOfBooks, book => {
if (book.stock > 111) book.title += ` -- 11% discount!`
})

View File

@@ -1,6 +1,5 @@
@server = http://localhost:4004
@me = Authorization: Basic {{$processEnv USER}}:
@alice = Authorization: Basic alice:
### ------------------------------------------------------------------------
@@ -17,11 +16,11 @@ GET {{server}}/browse/$metadata
### ------------------------------------------------------------------------
# Browse Books as any user
GET {{server}}/admin/Books?
GET {{server}}/browse/ListOfBooks?
# &$select=title,stock
&$expand=genre
# &sap-language=de
{{alice}}
{{me}}
### ------------------------------------------------------------------------
@@ -31,13 +30,13 @@ GET {{server}}/admin/Authors?
# &$expand=books($select=title;$expand=currency)
# &$filter=ID eq 101
# &sap-language=de
{{alice}}
Authorization: Basic alice:
### ------------------------------------------------------------------------
# Create Author
POST {{server}}/admin/Authors
Content-Type: application/json;IEEE754Compatible=true
{{alice}}
Authorization: Basic alice:
{
"ID": 112,
@@ -50,7 +49,7 @@ Content-Type: application/json;IEEE754Compatible=true
# Create book
POST {{server}}/admin/Books
Content-Type: application/json;IEEE754Compatible=true
{{alice}}
Authorization: Basic alice:
{
"ID": 2,
@@ -68,7 +67,7 @@ Content-Type: application/json;IEEE754Compatible=true
# Put image to books
PUT {{server}}/admin/Books(2)/image
Content-Type: image/png
{{alice}}
Authorization: Basic alice:
data:image/png;base64,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

View File

@@ -28,8 +28,8 @@ module.exports = async()=>{ // called by server.js
//
CatalogService.on ('OrderedBook', async (msg) => {
const { book, quantity, buyer } = msg.data
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
return OrdersService.tx(msg).create ('Orders').entries({
const { title, price } = await db.read (Books, book, b => { b.title, b.price })
return OrdersService.create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
buyer, createdBy: buyer

View File

@@ -4,7 +4,7 @@ using CatalogService from '@capire/bookstore';
//
// Books Object Page
//
annotate CatalogService.Book with @(UI : {
annotate CatalogService.Books with @(UI : {
HeaderInfo : {
TypeName : '{i18n>Book}',
TypeNamePlural : '{i18n>Books}',
@@ -24,7 +24,7 @@ annotate CatalogService.Book with @(UI : {
FieldGroup #Price : {Data : [
{Value : price},
{
Value : currencyName,
Value : currency.symbol,
Label : '{i18n>Currency}'
},
]},
@@ -35,11 +35,11 @@ annotate CatalogService.Book with @(UI : {
//
// Books List Page
//
annotate CatalogService.Book with @(UI : {
annotate CatalogService.Books with @(UI : {
SelectionFields : [
ID,
price,
currencyName
currency_code
],
LineItem : [
{
@@ -50,10 +50,8 @@ annotate CatalogService.Book with @(UI : {
Value : author,
Label : '{i18n>Author}'
},
{Value : genre},
{Value : genre.name},
{Value : price},
{Value : currencyName},
{Value : currency.symbol},
]
}) {
currencyName @Common.Label : '{i18n>Currency}';
};
}, );

View File

@@ -18,6 +18,7 @@ annotate my.Books with @(
ID,
author_ID,
price,
currency_code
],
LineItem : [
{ Value: ID, Label: '{i18n>Title}' },
@@ -25,6 +26,7 @@ annotate my.Books with @(
{ Value: genre.name },
{ Value: stock },
{ Value: price },
{ Value: currency.symbol },
]
}
) {
@@ -61,7 +63,7 @@ annotate my.Books with {
title @title: '{i18n>Title}';
genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title: '{i18n>Price}' @Measures.ISOCurrency : currency;
price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code;
stock @title: '{i18n>Stock}';
descr @title: '{i18n>Description}' @UI.MultiLineText;
image @title: '{i18n>Image}';

1
ord/cds-plugin.js Normal file
View File

@@ -0,0 +1 @@
// just a dummy tag file to be identified as a plugin

12
ord/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "@cap-js/ord",
"version": "2.0.0",
"cds": {
"requires": {
"SAP ORD Service": {
"model": "@cap-js/ord/srv/ord-service",
"service": "OrdService"
}
}
}
}

12
ord/srv/ord-service.cds Normal file
View File

@@ -0,0 +1,12 @@
// @requires: 'ORDconsumer'
@rest @path:'/ord/v1'
service OrdService {
@readonly entity documents {
key id: String;
}
@readonly entity csn {
key id: String;
}
function api (service: String, format: String) returns {};
}

73
ord/srv/ord-service.mjs Normal file
View File

@@ -0,0 +1,73 @@
import cds from '@sap/cds'
export class OrdService extends cds.ApplicationService {
init(){
this.on('READ','documents', req => {
let csn = cds.context?.model || cds.model
return { ord: csn }
})
/**
* Just an example to do something with id, if given.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/documents
* - http://localhost:4004/ord/v1/documents/CatalogService
* - http://localhost:4004/ord/v1/documents/CatalogService.Books
* - http://localhost:4004/ord/v1/documents/CatalogService.Authors
*/
this.on('READ','csn', req => {
let csn = cds.context?.model || cds.model
let { id } = req.data
if (id) csn = csn.definitions[id] || 'not in model!'
return { id, csn }
})
/**
* Just an example to serve arbitrary content with a function.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/api?service=CatalogService
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=edmx
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=edmx-v2
* - http://localhost:4004/ord/v1/api?service=CatalogService&format=openapi
*/
this.on('api', req => {
let csn = cds.context?.model || cds.model
let { service, format = 'csn' } = req.data
let { res } = req.http
if (format === 'csn') {
if (!service) return res.send(csn)
service = csn.services[service]
return res.send({ definitions: [ service, ...service.entities ] .reduce ((all,e) => {
let d = all[e.name] = {...e}
delete d.projection // not part of the API
delete d.query // not part of the API
return all
},{})})
}
let api = cds.compile(csn).to[format]({service})
return res.send(api)
})
/**
* Example how to register arbitrary express routes,
* and map them to our service's interface.
* Try it out with URLs like that:
*
* - http://localhost:4004/ord/v1/csn/CatalogService
* - http://localhost:4004/ord/v1/edmx/CatalogService
* - http://localhost:4004/ord/v1/openapi/CatalogService
* - http://localhost:4004/ord/v1/asyncapi/CatalogService
*
* NOTE: we add cds.middlewares.before to the route, which gives us all
* the context and auth handling, which is also available to CAP services.
*/
cds.app.get (`${this.path}/:api?/:service?`, cds.middlewares.before, req => {
const { api, service } = req.params
return this.api (service, api)
})
return super.init()
}
}

111
package-lock.json generated
View File

@@ -108,14 +108,14 @@
}
},
"node_modules/@cap-js-community/odata-v2-adapter": {
"version": "1.13.8",
"resolved": "https://registry.npmjs.org/@cap-js-community/odata-v2-adapter/-/odata-v2-adapter-1.13.8.tgz",
"integrity": "sha512-IYkUUJLMS8sNL+6H8NOT17pMKweItjEUqV9EctuHw8+pWsFLcK3GehztZrtPS9DfEBOD9tC/aPNU5+b5unmZrQ==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@cap-js-community/odata-v2-adapter/-/odata-v2-adapter-1.14.0.tgz",
"integrity": "sha512-L4yIHml7Pc3dpSiCCJkOADje7kXgMvAIhFx6ZdeWwBnniPiul++8zCbRMIIjjwFKuujTwjoprb7C4IzZdYlAfQ==",
"license": "Apache-2.0",
"dependencies": {
"body-parser": "^1.20.3",
"body-parser-xml": "^2.0.5",
"express": "^4.21.1",
"express": "^4.21.2",
"express-fileupload": "^1.5.1",
"http-proxy-middleware": "^3.0.3",
"xml2js": "^0.6.2"
@@ -125,17 +125,15 @@
}
},
"node_modules/@cap-js/cds-types": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@cap-js/cds-types/-/cds-types-0.8.0.tgz",
"integrity": "sha512-iy+Rc4C6tnFuBwTIREcrFBVp0vKVN+iB5WoZFcBX7b5y7rUvK9Pz/5YHplacyQpwzxUc8Iv+CXG6LeWH/b7Qqw==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@cap-js/cds-types/-/cds-types-0.9.0.tgz",
"integrity": "sha512-AD4WGAOOSszaleQQqheIo0hHm50zk3NejMlHsuG6cLh4EyK/kozvcx8hkWfAkUT/s11fa8OjyMhztFCy8b5DAA==",
"dev": true,
"hasInstallScript": true,
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@types/express": "^4.17.21"
},
"peerDependencies": {
"@sap/cds": "^8.0.0"
"@sap/cds": "^8.0.0",
"@types/express": ">=4"
}
},
"node_modules/@cap-js/db-service": {
@@ -152,9 +150,9 @@
}
},
"node_modules/@cap-js/sqlite": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-1.7.7.tgz",
"integrity": "sha512-SOmFJMr6pjWOniFRkJsrI0BpQEQT5Q8o+IVZ/LXFj6+bAe0NQQztzxhMQx62V/Px3u58JJM3xkPMU+QC5PcJHw==",
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-1.7.8.tgz",
"integrity": "sha512-llFn0LGNIdlsfU4KjzyuIMvlQhKxXodq4GIt9yStmmX/av/twwHR8SyUmTJirRH4IkNtpCsuNYpsI+bYO2Xklg==",
"dev": true,
"license": "SEE LICENSE",
"dependencies": {
@@ -259,11 +257,14 @@
}
},
"node_modules/@eslint/core": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz",
"integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@@ -293,9 +294,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.16.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
"integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -313,12 +314,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
"integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.10.0",
"levn": "^0.4.1"
},
"engines": {
@@ -389,9 +391,9 @@
}
},
"node_modules/@sap/cds": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.5.0.tgz",
"integrity": "sha512-mRiLBPcY5vC1xi21pPqzMtp1HY9FOOFajkiuSArqnwtiyp2fxqWVombuVE9lTB2UyBl9bL8XXwrpjzDftIbnBg==",
"version": "8.6.1",
"resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.6.1.tgz",
"integrity": "sha512-JYHRrGs6Tgle5Vmj/o3BaQkOBVcroweOrXhhiUVH6twISy+Yi2cWZdTr0EFFEt94FI1dVqvrVnEM67jEjOQImQ==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@sap/cds-compiler": ">=5.1",
@@ -464,6 +466,7 @@
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
@@ -475,6 +478,7 @@
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*"
}
@@ -491,6 +495,7 @@
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
@@ -504,6 +509,7 @@
"integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@@ -516,7 +522,8 @@
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/http-proxy": {
"version": "1.17.15",
@@ -537,7 +544,8 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/node": {
"version": "22.5.5",
@@ -553,14 +561,16 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
"integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/send": {
"version": "0.17.4",
@@ -568,6 +578,7 @@
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
@@ -579,6 +590,7 @@
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
@@ -694,9 +706,9 @@
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1303,19 +1315,19 @@
}
},
"node_modules/eslint": {
"version": "9.16.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
"integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0",
"@eslint/core": "^0.9.0",
"@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "9.16.0",
"@eslint/plugin-kit": "^0.2.3",
"@eslint/js": "9.19.0",
"@eslint/plugin-kit": "^0.2.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1",
@@ -1323,7 +1335,7 @@
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.5",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.2.0",
@@ -1479,9 +1491,10 @@
}
},
"node_modules/express": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -1502,7 +1515,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -1517,6 +1530,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-fileupload": {
@@ -2441,9 +2458,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/pathval": {

View File

@@ -1,51 +1,52 @@
{
"name": "@capire/samples",
"version": "2.1.0",
"description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com",
"dependencies": {
"@sap/cds": ">=8"
},
"workspaces": [
"./bookshop",
"./bookstore",
"./common",
"./data-viewer",
"./fiori",
"./hello",
"./media",
"./orders",
"./loggers",
"./reviews"
],
"devDependencies": {
"@cap-js/cds-types": "^0",
"@cap-js/sqlite": "^1",
"axios": "^1",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"eslint": "^9",
"semver": "^7"
},
"scripts": {
"bookshop": "cds watch bookshop",
"start": "cds watch fiori",
"fiori": "cds watch fiori",
"hello": "cds watch hello",
"media": "cds watch media",
"lint": "eslint",
"test": "npx jest --silent",
"jest": "npx jest --silent",
"mocha": "CDS_TEST_SILENT=y npx mocha",
"test:hello": "cd hello && npm test"
},
"mocha": {
"recursive": true,
"parallel": true,
"timeout": 6666
},
"license": "SEE LICENSE IN LICENSE",
"private": true
}
"name": "@capire/samples",
"version": "2.1.0",
"description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com",
"dependencies": {
"@sap/cds": ">=8"
},
"workspaces": [
"./bookshop",
"./bookstore",
"./common",
"./data-viewer",
"./fiori",
"./hello",
"./media",
"./ord",
"./orders",
"./loggers",
"./reviews"
],
"devDependencies": {
"@cap-js/cds-types": "^0",
"@cap-js/sqlite": "^1",
"axios": "^1",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"eslint": "^9",
"semver": "^7"
},
"scripts": {
"bookshop": "cds watch bookshop",
"start": "cds watch fiori",
"fiori": "cds watch fiori",
"hello": "cds watch hello",
"media": "cds watch media",
"lint": "eslint",
"test": "npx jest --silent",
"jest": "npx jest --silent",
"mocha": "CDS_TEST_SILENT=y npx mocha",
"test:hello": "cd hello && npm test"
},
"mocha": {
"recursive": true,
"parallel": true,
"timeout": 6666
},
"license": "SEE LICENSE IN LICENSE",
"private": true
}

View File

@@ -27,15 +27,15 @@ describe('cap/samples - Localized Data', () => {
})
it('supports queries with $expand', async () => {
const { data } = await GET(`/browse/Book?&$select=title,author,currencyName`, {
const { data } = await GET(`/browse/Books?&$select=title,author&$expand=currency`, {
headers: { 'Accept-Language': 'de' },
})
expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', author: 'Emily Brontë', currencyName: 'Pfund' },
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currencyName: 'Pfund' },
{ title: 'The Raven', author: 'Edgar Allen Poe', currencyName: 'US-Dollar' },
{ title: 'Eleonora', author: 'Edgar Allen Poe', currencyName: 'US-Dollar' },
{ title: 'Catweazle', author: 'Richard Carpenter', currencyName: 'Yen' },
{ title: 'Sturmhöhe', author: 'Emily Brontë', currency: { name: 'Pfund' } },
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } },
])
})

View File

@@ -12,20 +12,20 @@ describe('cap/samples - Bookshop APIs', () => {
'odata-version': '4.0',
})
expect(headers['content-type']).to.match(/application\/xml/)
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books"/>')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency Symbol"/>')
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
})
it('serves Books?$expand=genre,currency', async () => {
it('serves ListOfBooks?$expand=genre,currency', async () => {
const Mystery = { ID: 16, name: 'Mystery', descr: null, parent_ID: 10 }
const Romance = { ID: 15, name: 'Romance', descr: null, parent_ID: 10 }
const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' }
const { data } = await GET `/admin/Books ${{
const { data } = await GET `/browse/ListOfBooks ${{
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
}}`
expect(data.value).to.containSubset([
{ ID: 251, title: 'The Raven', author_ID: 150, genre:Mystery, currency:USD },
{ ID: 252, title: 'Eleonora', author_ID: 150, genre:Romance, currency:USD },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD },
])
})