├── .cdsrc.json
├── srv
├── async
│ ├── file.txt
│ ├── file2.txt
│ ├── async.js
│ ├── databaseAsync.js
│ ├── databaseAsync2.js
│ ├── fileSync.js
│ ├── httpClient2.js
│ ├── fileAsync.js
│ ├── httpClient.js
│ └── database.js
├── _i18n
│ ├── messages_ja.properties
│ ├── messages.properties
│ └── messages_de.properties
├── utils
│ ├── myModule.js
│ ├── auth.js
│ ├── json.js
│ ├── locale.js
│ ├── exampleTOC.js
│ └── general.js
├── admin-service.cds
├── oo
│ ├── ooTutorial4.js
│ ├── ooTutorial1.js
│ ├── ooTutorial3.js
│ └── ooTutorial2.js
├── routes
│ ├── hello.js
│ ├── ex1.js
│ ├── swagger.js
│ ├── textBundle.js
│ ├── xml.js
│ ├── user.js
│ ├── multiply.js
│ ├── os.js
│ ├── smtp.js
│ ├── chatServer.js
│ ├── excel.js
│ ├── outboundTest.js
│ ├── await.js
│ ├── exerciseAsync.js
│ ├── oo.js
│ ├── zip.js
│ ├── es6.js
│ ├── es2018.js
│ └── ex2.js
├── admin-service.js
├── server
│ ├── express.js
│ ├── swagger.js
│ ├── healthCheck.js
│ ├── overloadProtection.js
│ └── expressSecurity.js
├── cat-service.cds
├── cat-service.js
└── server.js
├── app
├── resources
│ ├── common
│ │ ├── css
│ │ │ └── style.css
│ │ ├── images
│ │ │ ├── favicon.ico
│ │ │ └── sap_18.png
│ │ ├── view
│ │ │ └── BusyDialog.fragment.xml
│ │ ├── controller
│ │ │ ├── handler.js
│ │ │ └── BaseController.js
│ │ ├── model
│ │ │ └── models.js
│ │ ├── Component.js
│ │ └── index.js
│ ├── multiply
│ │ ├── i18n
│ │ │ └── i18n.properties
│ │ ├── view
│ │ │ └── App.view.xml
│ │ ├── Component.js
│ │ ├── index.html
│ │ ├── controller
│ │ │ └── App.controller.js
│ │ └── manifest.json
│ ├── favicon.ico
│ ├── exerciseAsync
│ │ ├── i18n
│ │ │ └── i18n.properties
│ │ ├── view
│ │ │ └── App.view.xml
│ │ ├── Component.js
│ │ ├── index.html
│ │ ├── manifest.json
│ │ └── controller
│ │ │ └── App.controller.js
│ ├── exerciseChat
│ │ ├── i18n
│ │ │ └── i18n.properties
│ │ ├── view
│ │ │ └── App.view.xml
│ │ ├── index.html
│ │ ├── Component.js
│ │ ├── controller
│ │ │ └── App.controller.js
│ │ └── manifest.json
│ ├── index.html
│ └── logout.html
├── favicon.ico
├── admin-books
│ ├── webapp
│ │ ├── i18n
│ │ │ └── i18n.properties
│ │ ├── Component.js
│ │ └── manifest.json
│ └── fiori-service.cds
├── services.cds
├── browse
│ ├── webapp
│ │ ├── i18n
│ │ │ └── i18n.properties
│ │ ├── Component.js
│ │ └── manifest.json
│ └── fiori-service.cds
├── _i18n
│ ├── i18n.properties
│ └── i18n_de.properties
├── package.json
├── indexBookshop.html
├── appconfig
│ └── fioriSandboxConfig.json
├── indexFLP.html
├── xs-app.json
└── common.cds
├── .gitattributes
├── tsconfig.json
├── db
├── data
│ ├── sap.capire.bookshop-Genres.csv
│ ├── sap.capire.bookshop-Authors.csv
│ ├── sap.capire.bookshop-Books_texts.csv
│ └── sap.capire.bookshop-Books.csv
└── schema.cds
├── .gitignore
├── .vscode
├── settings.json
├── launch.json
├── tasks.json
└── extensions.json
├── .eslintrc
├── REUSE.toml
├── README.md
├── package.json
├── LICENSE
└── LICENSES
└── Apache-2.0.txt
/.cdsrc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/srv/async/file.txt:
--------------------------------------------------------------------------------
1 | This is my first file
--------------------------------------------------------------------------------
/srv/async/file2.txt:
--------------------------------------------------------------------------------
1 | This is my second file
--------------------------------------------------------------------------------
/app/resources/common/css/style.css:
--------------------------------------------------------------------------------
1 | /* Enter your custom styles here */
--------------------------------------------------------------------------------
/srv/_i18n/messages_ja.properties:
--------------------------------------------------------------------------------
1 | greeting = こんにちは!{0}上で実行されているへようこそ{1}
--------------------------------------------------------------------------------
/srv/_i18n/messages.properties:
--------------------------------------------------------------------------------
1 | greeting = Hello! Welcome to {0} running on {1}.
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/srv/_i18n/messages_de.properties:
--------------------------------------------------------------------------------
1 | greeting = Hallo! Willkommen bei {0} läuft auf {1} .
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/cloud-cap-with-javascript-basics/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/srv/utils/myModule.js:
--------------------------------------------------------------------------------
1 | export function helloModule() {
2 | return "Hello World from My First Module"
3 | }
--------------------------------------------------------------------------------
/app/resources/multiply/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | title=Title
2 | appTitle=Multiply Exericse
3 | appDescription=Multiply Exericse
--------------------------------------------------------------------------------
/app/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/cloud-cap-with-javascript-basics/HEAD/app/resources/favicon.ico
--------------------------------------------------------------------------------
/app/resources/common/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/cloud-cap-with-javascript-basics/HEAD/app/resources/common/images/favicon.ico
--------------------------------------------------------------------------------
/app/resources/common/images/sap_18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP-samples/cloud-cap-with-javascript-basics/HEAD/app/resources/common/images/sap_18.png
--------------------------------------------------------------------------------
/app/admin-books/webapp/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | appTitle=Manage Books
2 | appSubTitle=Manage bookshop inventory
3 | appDescription=Manage your bookshop inventory with ease.
4 |
--------------------------------------------------------------------------------
/srv/async/async.js:
--------------------------------------------------------------------------------
1 | export default function (wss) {
2 | wss.broadcast("Start")
3 | setTimeout(() => {
4 | wss.broadcast("Wait Timer Over")
5 | }, 3000)
6 | wss.broadcast("End")
7 | }
--------------------------------------------------------------------------------
/app/services.cds:
--------------------------------------------------------------------------------
1 | /*
2 | This model controls what gets served to Fiori frontends...
3 | */
4 | using from './common';
5 | using from './browse/fiori-service';
6 | using from './admin-books/fiori-service';
7 |
--------------------------------------------------------------------------------
/app/browse/webapp/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | appTitle=Browse Books
2 | appSubTitle=Find all your favorite books
3 | appDescription=This application lets you find the next books you want to read.
4 | appInfo=This is an app info field
5 |
--------------------------------------------------------------------------------
/srv/admin-service.cds:
--------------------------------------------------------------------------------
1 | using { sap.capire.bookshop as my } from '../db/schema';
2 | service AdminService @(requires:'admin') {
3 | entity Books as projection on my.Books;
4 | entity Authors as projection on my.Authors;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/srv/async/databaseAsync.js:
--------------------------------------------------------------------------------
1 | import * as db from './database.js'
2 |
3 | export default function (wss) {
4 | wss.broadcast("Before Database Call")
5 | db.callDB1(wss)
6 | db.callDB2(wss)
7 | wss.broadcast("After Database Call")
8 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "nodenext",
4 | "module": "NodeNext",
5 | "paths": {
6 | "#cds-models/*": [
7 | "./@cds-models/*/index.ts"
8 | ]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/browse/webapp/Component.js:
--------------------------------------------------------------------------------
1 | sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
2 | "use strict";
3 | return AppComponent.extend("bookshop.Component", {
4 | metadata: { manifest: "json" }
5 | });
6 | });
7 | /* eslint no-undef:0 */
8 |
--------------------------------------------------------------------------------
/app/resources/common/view/BusyDialog.fragment.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/app/admin-books/webapp/Component.js:
--------------------------------------------------------------------------------
1 | sap.ui.define(["sap/fe/core/AppComponent"], function (AppComponent) {
2 | "use strict";
3 | return AppComponent.extend("books.Component", {
4 | metadata: { manifest: "json" }
5 | });
6 | });
7 |
8 | /* eslint no-undef:0 */
9 |
--------------------------------------------------------------------------------
/srv/utils/auth.js:
--------------------------------------------------------------------------------
1 | export default function(req) {
2 | let accessToken = null
3 | if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
4 | accessToken = req.headers.authorization.split(" ")[1]
5 | }
6 | return accessToken
7 | }
--------------------------------------------------------------------------------
/app/_i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | Books = Books
2 | Book = Book
3 | ID = ID
4 | Title = Title
5 | Author = Author
6 | Authors = Authors
7 | AuthorID = Author ID
8 | AuthorName = Author Name
9 | Name = Name
10 | Age = Age
11 | Stock = Stock
12 | Order = Order
13 | Orders = Orders
14 | Price = Price
15 |
--------------------------------------------------------------------------------
/app/_i18n/i18n_de.properties:
--------------------------------------------------------------------------------
1 | Books = Bücher
2 | Book = Buch
3 | ID = ID
4 | Title = Titel
5 | Author = Autor
6 | Authors = Autoren
7 | AuthorID = ID des Autors
8 | AuthorName = Name des Autors
9 | Name = Name
10 | Age = Alter
11 | Stock = Bestand
12 | Order = Bestellung
13 | Orders = Bestellungen
14 | Price = Preis
15 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-approuter",
3 | "dependencies": {
4 | "@sap/approuter": "^14",
5 | "@sap/html5-repo-mock": "^2.1.5",
6 | "dotenv": "^16"
7 | },
8 | "scripts": {
9 | "start": "node node_modules/@sap/approuter/approuter.js",
10 | "start-local": "node node_modules/@sap/html5-repo-mock/index.js"
11 | }
12 | }
--------------------------------------------------------------------------------
/db/data/sap.capire.bookshop-Genres.csv:
--------------------------------------------------------------------------------
1 | ID;parent_ID;name
2 | 10;;Fiction
3 | 11;10;Drama
4 | 12;10;Poetry
5 | 13;10;Fantasy
6 | 14;10;Science Fiction
7 | 15;10;Romance
8 | 16;10;Mystery
9 | 17;10;Thriller
10 | 18;10;Dystopia
11 | 19;10;Fairy Tale
12 | 20;;Non-Fiction
13 | 21;20;Biography
14 | 22;21;Autobiography
15 | 23;20;Essay
16 | 24;20;Speech
17 |
--------------------------------------------------------------------------------
/srv/async/databaseAsync2.js:
--------------------------------------------------------------------------------
1 | import * as db from './database.js'
2 |
3 | export default async function (wss) {
4 | wss.broadcast("Before Database Call")
5 | await Promise.all([
6 | db.callDB3(wss),
7 | db.callDB4(wss)
8 | ])
9 | wss.broadcast("After Database Call")
10 | wss.broadcast("---Everything's Really Done Now. Go Home!---")
11 | }
--------------------------------------------------------------------------------
/srv/oo/ooTutorial4.js:
--------------------------------------------------------------------------------
1 | import ooTutorial3 from "./ooTutorial3.js";
2 | export default class extends ooTutorial3 {
3 | constructor(title) {
4 | super(title)
5 | }
6 |
7 | async calculateBookPrice() {
8 | let newPrice = await super.calculateBookPrice()
9 | newPrice.price = newPrice.price * 1.25
10 | return newPrice
11 | }
12 | }
--------------------------------------------------------------------------------
/srv/oo/ooTutorial1.js:
--------------------------------------------------------------------------------
1 | export default class {
2 | constructor(id) {
3 | this.id = id
4 | }
5 | myFirstMethod(import1) {
6 | if (import1 === 20) {
7 | let error = {}
8 | error.message = "Something Bad Happened"
9 | error.value = import1
10 | throw error
11 | }
12 | return import1
13 | }
14 | }
--------------------------------------------------------------------------------
/srv/routes/hello.js:
--------------------------------------------------------------------------------
1 | export function load (app) {
2 | /**
3 | * @swagger
4 | *
5 | * /rest/hello:
6 | * get:
7 | * summary: Test Endpoint
8 | * tags:
9 | * - Test
10 | * responses:
11 | * '200':
12 | * description: Hello World
13 | */
14 | app.get('/rest/hello', function (req, res) {
15 | return res.send('Hello World!')
16 | })
17 | }
--------------------------------------------------------------------------------
/app/resources/common/controller/handler.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | "use strict";
4 | sap.ui.define([
5 | "sap/m/MessageToast",
6 | ], function(MessageToast) {
7 | "use strict";
8 |
9 | return function(controller) {
10 | const message = `Pressed from ${controller.getMetadata().getName()}`
11 | MessageToast.show(message)
12 | }
13 | })
--------------------------------------------------------------------------------
/srv/utils/json.js:
--------------------------------------------------------------------------------
1 | /**
2 | @function Puts a JSON object into the Response Object
3 | @param {object} jsonOut - JSON Object
4 | @param {object} res - response object from Express
5 | */
6 | export default function(jsonOut, res) {
7 | let out = []
8 | for (let item of jsonOut) {
9 | out.push(item)
10 | }
11 | res.type("application/json").status(200).send(JSON.stringify(out))
12 | return
13 | }
--------------------------------------------------------------------------------
/db/data/sap.capire.bookshop-Authors.csv:
--------------------------------------------------------------------------------
1 | ID;name;dateOfBirth;placeOfBirth;dateOfDeath;placeOfDeath
2 | 101;Emily Brontë;1818-07-30;Thornton, Yorkshire;1848-12-19;Haworth, Yorkshire
3 | 107;Charlotte Brontë;1818-04-21;Thornton, Yorkshire;1855-03-31;Haworth, Yorkshire
4 | 150;Edgar Allen Poe;1809-01-19;Boston, Massachusetts;1849-10-07;Baltimore, Maryland
5 | 170;Richard Carpenter;1929-08-14;King’s Lynn, Norfolk;2012-02-26;Hertfordshire, England
6 |
--------------------------------------------------------------------------------
/srv/routes/ex1.js:
--------------------------------------------------------------------------------
1 | export function load(app) {
2 | /**
3 | * @swagger
4 | *
5 | * /rest/ex1:
6 | * get:
7 | * summary: Hello World
8 | * tags:
9 | * - exercises
10 | * responses:
11 | * '200':
12 | * description: Output
13 | */
14 | app.get("/rest/ex1", (req, res) => {
15 | res.send("Hello World Node.js")
16 | })
17 | return app
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CAP cap-with-javascript-basics
2 | _out
3 | *.db
4 | *.sqlite
5 | connection.properties
6 | default-*.json
7 | .cdsrc-private.json
8 | gen/
9 | node_modules/
10 | target/
11 |
12 | # Web IDE, App Studio
13 | .che/
14 | .gen/
15 |
16 | # MTA
17 | *_mta_build_tmp
18 | *.mtar
19 | mta_archives/
20 |
21 | # Other
22 | .DS_Store
23 | *.orig
24 | *.log
25 |
26 | *.iml
27 | *.flattened-pom.xml
28 |
29 | # IDEs
30 | # .vscode
31 | # .idea
32 |
33 | @cds-models
--------------------------------------------------------------------------------
/app/resources/common/model/models.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | sap.ui.define([
4 | "sap/ui/model/json/JSONModel",
5 | "sap/ui/Device"
6 | ], (JSONModel, Device) => {
7 | "use strict"
8 |
9 | return {
10 |
11 | createDeviceModel: () => {
12 | var oModel = new JSONModel(Device)
13 | oModel.setDefaultBindingMode("OneWay")
14 | return oModel
15 | }
16 |
17 | }
18 | })
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "SAP HANA Database Explorer.displaySapWebAnalyticsStartupNotification": false,
3 | "SAP HANA Database Artifacts.displaySapWebAnalyticsStartupNotification": false,
4 | "cSpell.words": [
5 | "Catweazle",
6 | "cloudnative",
7 | "healthcheck",
8 | "langparser",
9 | "ondemand",
10 | "schemaname",
11 | "tablename",
12 | "timsestamp",
13 | "Wuthering"
14 | ]
15 | }
--------------------------------------------------------------------------------
/srv/admin-service.js:
--------------------------------------------------------------------------------
1 | import cds from '@sap/cds'
2 |
3 | class AdminService extends cds.ApplicationService { init(){
4 | this.before ('NEW','Books.drafts', genid)
5 | return super.init()
6 | }}
7 |
8 | /** Generate primary keys for target entity in request */
9 | async function genid (req) {
10 | const {ID} = await cds.tx(req).run (SELECT.one.from(req.target.actives).columns('max(ID) as ID'))
11 | req.data.ID = ID - ID % 100 + 100 + 1
12 | }
13 |
14 | export { AdminService }
15 |
--------------------------------------------------------------------------------
/srv/async/fileSync.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { fileURLToPath } from 'url'
3 | const __dirname = fileURLToPath(new URL('.', import.meta.url))
4 | import * as path from 'path'
5 |
6 | export default function (wss) {
7 | let text = fs.readFileSync(path.join(__dirname, "./file.txt"), "utf8")
8 | wss.broadcast(text)
9 |
10 | wss.broadcast("After First Read\n")
11 |
12 | text = fs.readFileSync(path.join(__dirname, "./file2.txt"), "utf8")
13 | wss.broadcast(text)
14 |
15 | wss.broadcast("After Second Read\n")
16 | }
--------------------------------------------------------------------------------
/srv/async/httpClient2.js:
--------------------------------------------------------------------------------
1 | import request from 'then-request'
2 |
3 | export default async function (wss) {
4 | wss.broadcast("Before HTTP Call\n")
5 | try {
6 | let data = await request('GET', `http://www.loc.gov/pictures/search/?fo=json&q=SAP&`)
7 | let jsonObj = JSON.parse(data.getBody())
8 | wss.broadcast(JSON.stringify(jsonObj).substring(0, 100))
9 | } catch (err) {
10 | cds.log('nodejs').error(err)
11 | wss.broadcast(err.toString())
12 | }
13 | wss.broadcast("After HTTP Call\n")
14 | }
--------------------------------------------------------------------------------
/srv/server/express.js:
--------------------------------------------------------------------------------
1 | import {swagger} from './swagger.js'
2 | import expressSecurity from './expressSecurity.js'
3 | import healthCheck from './healthCheck.js'
4 | import overloadProtection from './overloadProtection.js'
5 | import expressStatusMonitor from 'express-status-monitor'
6 |
7 | export default function (app, cds) {
8 | app.log = cds.log('nodejs')
9 | app.cds = cds
10 | app.log(`Custom Express Startup`)
11 | app.swagger = new swagger()
12 | expressSecurity(app)
13 |
14 | healthCheck(app)
15 | overloadProtection(app)
16 | app.use(expressStatusMonitor())
17 |
18 | }
--------------------------------------------------------------------------------
/srv/routes/swagger.js:
--------------------------------------------------------------------------------
1 | import swaggerUI from 'swagger-ui-express'
2 | export async function load (app) {
3 | //const swaggerUi = require('swagger-ui-express')
4 | const swaggerSpec = await app.swagger.getOpenAPI()
5 |
6 | app.get('/apiJS/api-docs.json', function (req, res) {
7 | res.setHeader('Content-Type', 'application/json')
8 | res.send(swaggerSpec)
9 | })
10 | let options = {
11 | explorer: true,
12 | swaggerOptions: {
13 | docExpansion: "none",
14 | basePath: '/apiJS'
15 | }
16 | }
17 | app.use('/apiJS/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerSpec, options))
18 | }
--------------------------------------------------------------------------------
/srv/server/swagger.js:
--------------------------------------------------------------------------------
1 | import swaggerJSDoc from 'swagger-jsdoc'
2 | export function swagger() {
3 | this.getOpenAPI = async () => {
4 | let options = {
5 | swaggerDefinition: {
6 | openapi: '3.0.0',
7 | info: {
8 | title: 'JavaScript Nodejs Examples',
9 | version: '1.0.0'
10 | },
11 | },
12 | apis: ['./srv/routes/*']
13 | }
14 | var swaggerSpec = swaggerJSDoc(options)
15 | swaggerSpec.components.requestBodies = []
16 | return swaggerSpec
17 | }
18 | }
--------------------------------------------------------------------------------
/srv/utils/locale.js:
--------------------------------------------------------------------------------
1 | import langparser from "accept-language-parser"
2 | export function getLocaleReq(req){
3 |
4 | let lang = req.headers["accept-language"]
5 | if (!lang) {
6 | return 'en-US'
7 | }
8 | var arr = langparser.parse(lang)
9 | if (!arr || arr.length < 1) {
10 | return 'en-US'
11 | }
12 | var locale = arr[0].code
13 | if (arr[0].region) {
14 | locale += "-" + arr[0].region
15 | }
16 | return locale
17 | }
18 |
19 | export function getLocale(env){
20 | env = env || process.env
21 | return env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE
22 | }
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | title=Title
2 |
3 | #XTIT: Application name
4 | appTitle=Exercise Node.js Async
5 |
6 | #YDES: Application description
7 | appDescription=Exercise Node.js Async
8 | #~~~ Not Found View ~~~~~~~~~~~~~~~~~~~~~~~
9 |
10 | #XTIT: Not found view title
11 | notFoundTitle=Not Found
12 |
13 | #YMSG: The not found text is displayed when there was an error loading the resource (404 error)
14 | notFoundText=The requested resource was not found
15 |
16 | #~~~ Error Handling ~~~~~~~~~~~~~~~~~~~~~~~
17 |
18 | #YMSG: Error dialog description
19 | errorText=Sorry, a technical error occurred! Please try again later.
--------------------------------------------------------------------------------
/app/resources/exerciseChat/i18n/i18n.properties:
--------------------------------------------------------------------------------
1 | title=Title
2 |
3 | #XTIT: Application name
4 | appTitle=Node.js Web Sockets Chat
5 |
6 | #YDES: Application description
7 | appDescription=Node.js Web Sockets Chat
8 | #~~~ Not Found View ~~~~~~~~~~~~~~~~~~~~~~~
9 |
10 | #XTIT: Not found view title
11 | notFoundTitle=Not Found
12 |
13 | #YMSG: The not found text is displayed when there was an error loading the resource (404 error)
14 | notFoundText=The requested resource was not found
15 |
16 | #~~~ Error Handling ~~~~~~~~~~~~~~~~~~~~~~~
17 |
18 | #YMSG: Error dialog description
19 | errorText=Sorry, a technical error occurred! Please try again later.
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "cds serve",
9 | "request": "launch",
10 | "type": "node",
11 | "cwd": "${workspaceFolder}",
12 | "runtimeExecutable": "cds",
13 | "args": [
14 | "serve",
15 | "--with-mocks",
16 | "--in-memory?"
17 | ],
18 | "skipFiles": [
19 | "/**"
20 | ]
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "shell",
8 | "label": "cds watch",
9 | "command": "cds",
10 | "args": ["watch"],
11 | "group": {
12 | "kind": "build",
13 | "isDefault": true
14 | },
15 | "problemMatcher": []
16 | },
17 | {
18 | "type": "shell",
19 | "label": "cds serve",
20 | "command": "cds",
21 | "args": ["serve", "--with-mocks", "--in-memory?"],
22 | "problemMatcher": []
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/srv/cat-service.cds:
--------------------------------------------------------------------------------
1 | using { sap.capire.bookshop as my } from '../db/schema';
2 |
3 | @protocol: [
4 | 'odata-v4',
5 | 'graphql'
6 | ]
7 | service CatalogService {
8 |
9 | /** For displaying lists of Books */
10 | @readonly entity ListOfBooks as projection on Books
11 | excluding { descr };
12 |
13 | @readonly entity Books as projection on my.Books { *,
14 | author.name as author
15 | } excluding { createdBy, modifiedBy };
16 |
17 | @requires: 'authenticated-user'
18 | action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
19 | event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4 |
5 | // List of extensions which should be recommended for users of this workspace.
6 | "recommendations": [
7 | "SAPSE.vscode-cds",
8 | "dbaeumer.vscode-eslint",
9 | "esbenp.prettier-vscode",
10 | "mechatroner.rainbow-csv",
11 | "qwtel.sqlite-viewer",
12 | "humao.rest-client",
13 | "sdras.night-owl"
14 | ],
15 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
16 | "unwantedRecommendations": [
17 |
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/srv/async/fileAsync.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { fileURLToPath } from 'url'
3 | const __dirname = fileURLToPath(new URL('.', import.meta.url))
4 | import * as path from 'path'
5 |
6 | export default function (wss) {
7 | fs.readFile(path.join(__dirname, "./file.txt"), "utf8", (error, text) => {
8 | if(error){
9 | cds.log('nodejs').error(error)
10 | }
11 | wss.broadcast(text)
12 | })
13 | wss.broadcast("After First Read\n")
14 |
15 | fs.readFile(path.join(__dirname, "./file2.txt"), "utf8", (error, text) => {
16 | if(error){
17 | cds.log('nodejs').error(error)
18 | }
19 | wss.broadcast(text)
20 | })
21 | wss.broadcast("After Second Read\n")
22 | }
--------------------------------------------------------------------------------
/app/resources/multiply/view/App.view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/resources/common/Component.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | sap.ui.define([
4 | "sap/ui/core/UIComponent",
5 | "sap/ui/Device",
6 | "sap/sample/common/model/models"
7 | ], function (UIComponent, Device, models) {
8 | "use strict"
9 |
10 | return UIComponent.extend("sap.sample.common.Component", {
11 |
12 | superInit() {
13 |
14 | // call the base component's init function
15 | UIComponent.prototype.init.apply(this, arguments)
16 |
17 | // enable routing
18 | this.getRouter().initialize()
19 |
20 | // set the device model
21 | this.setModel(models.createDeviceModel(), "device")
22 | }
23 |
24 | })
25 | })
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "env": {
4 | "es2023": true,
5 | "es2022": true,
6 | "node": true,
7 | "jest": true,
8 | "mocha": true,
9 | "commonjs": false,
10 | "es6": true
11 | },
12 | "globals": {
13 | "SELECT": true,
14 | "INSERT": true,
15 | "UPSERT": true,
16 | "UPDATE": true,
17 | "DELETE": true,
18 | "CREATE": true,
19 | "DROP": true,
20 | "CDL": true,
21 | "CQL": true,
22 | "CXL": true,
23 | "cds": true,
24 | "Atomics": "readonly",
25 | "SharedArrayBuffer": "readonly"
26 | },
27 | "parserOptions": {
28 | "ecmaVersion": 2023,
29 | "sourceType": "module"
30 | },
31 | "rules": {
32 | "no-console": "off",
33 | "require-atomic-updates": "off"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/srv/async/httpClient.js:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | export default function (wss) {
3 | wss.broadcast("Before HTTP Call\n")
4 | try {
5 | http.get({
6 | path: "http://www.loc.gov/pictures/search/?fo=json&q=SAP&",
7 | host: "www.loc.gov",
8 | port: "80",
9 | headers: {
10 | host: "www.loc.gov"
11 | }
12 | },
13 | (response) => {
14 | response.setEncoding("utf8")
15 | response.on("data", (data) => {
16 | wss.broadcast(data.substring(0, 100))
17 | })
18 | response.on("error", wss.broadcast)
19 | })
20 | } catch (err) {
21 | cds.log('nodejs').error(err)
22 | wss.broadcast(err.toString())
23 | }
24 | wss.broadcast("After HTTP Call\n")
25 | }
--------------------------------------------------------------------------------
/srv/routes/textBundle.js:
--------------------------------------------------------------------------------
1 | import textbundle from '@sap/textbundle'
2 | import {getLocaleReq} from '../utils/locale.js'
3 | import os from 'os'
4 |
5 | export function load(app){
6 | /**
7 | * @swagger
8 | *
9 | * /rest/textBundle:
10 | * get:
11 | * summary: Language Dependent Text Bundles
12 | * tags:
13 | * - textBundle
14 | * responses:
15 | * '200':
16 | * description: Text
17 | */
18 |
19 | app.get("/rest/textBundle", (req, res) => {
20 | const TextBundle = textbundle.TextBundle
21 | var bundle = new TextBundle(global.__base + "_i18n/messages", getLocaleReq(req))
22 | res.writeHead(200, {
23 | "Content-Type": "text/plain; charset=utf-8"
24 | })
25 | var greeting = bundle.getText("greeting", [os.hostname(), os.type()])
26 | res.end(greeting, "utf-8")
27 | })
28 | return app
29 | }
--------------------------------------------------------------------------------
/srv/server/healthCheck.js:
--------------------------------------------------------------------------------
1 | import health from '@cloudnative/health-connect'
2 | import lag from 'event-loop-lag'
3 |
4 | export default function (app) {
5 | app.log(`Custom health checks at /healthcheck`)
6 | let healthcheck = new health.HealthChecker()
7 | const lagHealth = () => new Promise((resolve, _reject) => {
8 | let lagCheck = lag(1000)
9 | if(lagCheck() > 40){
10 | _reject(`Event Loop Lag Exceeded: ${lagCheck()} milliseconds`)
11 | }
12 | resolve()
13 | })
14 | let lagCheck = new health.LivenessCheck("Event Loop Lag Check", lagHealth)
15 | healthcheck.registerLivenessCheck(lagCheck)
16 | app.use('/live', health.LivenessEndpoint(healthcheck))
17 | app.use('/ready', health.ReadinessEndpoint(healthcheck))
18 | app.use('/health', health.HealthEndpoint(healthcheck))
19 | app.use('/healthcheck', health.HealthEndpoint(healthcheck))
20 | }
--------------------------------------------------------------------------------
/srv/cat-service.js:
--------------------------------------------------------------------------------
1 | import cds from '@sap/cds'
2 | const { Books } = cds.entities ('sap.capire.bookshop')
3 |
4 | class CatalogService extends cds.ApplicationService { init(){
5 |
6 | // Reduce stock of ordered books if available stock suffices
7 | this.on ('submitOrder', async req => {
8 | const {book,quantity} = req.data
9 | let {stock} = await SELECT `stock` .from (Books,book)
10 | if (stock >= quantity) {
11 | await UPDATE (Books,book) .with (`stock -=`, quantity)
12 | await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
13 | return { stock }
14 | }
15 | else return req.error (409,`${quantity} exceeds stock for book #${book}`)
16 | })
17 |
18 | // Add some discount for overstocked books
19 | this.after ('READ','ListOfBooks', each => {
20 | if (each.stock > 111) each.title += ` -- 11% discount!`
21 | })
22 |
23 | return super.init()
24 | }}
25 | export { CatalogService }
26 |
--------------------------------------------------------------------------------
/db/schema.cds:
--------------------------------------------------------------------------------
1 | using { Currency, managed, sap } from '@sap/cds/common';
2 | namespace sap.capire.bookshop;
3 |
4 | entity Books : managed {
5 | key ID : Integer;
6 | @mandatory title : localized String(111);
7 | descr : localized String(1111);
8 | @mandatory author : Association to Authors;
9 | genre : Association to Genres;
10 | stock : Integer;
11 | price : Decimal;
12 | currency : Currency;
13 | image : LargeBinary @Core.MediaType : 'image/png';
14 | }
15 |
16 | entity Authors : managed {
17 | key ID : Integer;
18 | @mandatory name : String(111);
19 | dateOfBirth : Date;
20 | dateOfDeath : Date;
21 | placeOfBirth : String;
22 | placeOfDeath : String;
23 | books : Association to many Books on books.author = $self;
24 | }
25 |
26 | /** Hierarchically organized Code List for Genres */
27 | entity Genres : sap.common.CodeList {
28 | key ID : Integer;
29 | parent : Association to Genres;
30 | children : Composition of many Genres on children.parent = $self;
31 | }
32 |
--------------------------------------------------------------------------------
/srv/server/overloadProtection.js:
--------------------------------------------------------------------------------
1 | import overload from 'overload-protection'
2 | export default function (app) {
3 | app.log(`Adding Overload Protection`)
4 | const protectCfg = {
5 | production: process.env.NODE_ENV === 'production', // if production is false, detailed error messages are exposed to the client
6 | clientRetrySecs: 1, // Client-Retry header, in seconds (0 to disable) [default 1]
7 | sampleInterval: 5, // sample rate, milliseconds [default 5]
8 | maxEventLoopDelay: 100, // maximum detected delay between event loop ticks [default 42]
9 | maxHeapUsedBytes: 0, // maximum heap used threshold (0 to disable) [default 0]
10 | maxRssBytes: 0, // maximum rss size threshold (0 to disable) [default 0]
11 | errorPropagationMode: false, // dictate behavior: take over the response
12 | logging: (message)=>{
13 | app.log(message)
14 | }
15 | // or propagate an error to the framework [default false]
16 | }
17 | const protect = overload('express', protectCfg)
18 | app.use(protect)
19 | }
--------------------------------------------------------------------------------
/app/indexBookshop.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Bookshop
9 |
10 |
16 |
17 |
18 |
24 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/resources/common/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint no-console: 0, no-unused-vars: 0, no-use-before-define: 0, no-redeclare: 0, no-shadow:0 */
3 | /*eslint-env es6 */
4 | sap.ui.require(["sap/ui/core/Core", "sap/ui/core/Component"], (oCore, Component) => {
5 |
6 | Component.create({
7 | id: "comp",
8 | name: "root",
9 | manifestFirst: true,
10 | async: true
11 | }).then((oComp) => {
12 | sap.ui.require(["sap/ui/core/ComponentContainer"], (ComponentContainer) => {
13 | let oCont = new ComponentContainer({
14 | component: oComp,
15 | height: "100%"
16 | })
17 |
18 | oCore.loadLibrary("sap.f", {
19 | async: true
20 | }).then(() => {
21 | let oShell = new sap.f.ShellBar({
22 | id: "myShell",
23 | homeIcon: "../common/images/sap_18.png",
24 | showCopilot: true,
25 | showSearch: true,
26 | showNotifications: true,
27 | showProductSwitcher: true,
28 | profile: new sap.f.Avatar({
29 | initials: ""
30 | })
31 | }).placeAt("content")
32 | oCont.placeAt("content")
33 | })
34 | })
35 | })
36 |
37 | })
--------------------------------------------------------------------------------
/app/resources/exerciseChat/view/App.view.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/resources/multiply/Component.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | sap.ui.define([
4 | "sap/sample/common/Component"
5 | ], function (UIComponent) {
6 | "use strict";
7 |
8 | return UIComponent.extend("sap.sample.multiply.Component", {
9 |
10 | metadata: {
11 | manifest: "json"
12 | },
13 |
14 | createContent: function () {
15 | // create root view
16 | var oView = sap.ui.view({
17 | id: "App",
18 | viewName: `sap.sample.multiply.view.App`,
19 | type: "XML",
20 | async: true,
21 | viewData: {
22 | component: this
23 | }
24 | })
25 | return oView
26 | },
27 |
28 | /**
29 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
30 | * @public
31 | * @override
32 | */
33 | init: function () {
34 | this.superInit()
35 | }
36 | })
37 | })
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/view/App.view.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/srv/routes/xml.js:
--------------------------------------------------------------------------------
1 | import {XmlDocument} from 'xmldoc'
2 | import toc from '../utils/exampleTOC.js'
3 |
4 | export function load(app){
5 |
6 | app.get("/rest/xml", (req, res) => {
7 | var output = `XML Examples
8 | /example1 - Simple XML parsing` +
9 | toc()
10 | res.type("text/html").status(200).send(output)
11 | })
12 |
13 | /**
14 | * @swagger
15 | *
16 | * /rest/xml/example1:
17 | * get:
18 | * summary: Parse XML
19 | * tags:
20 | * - xml
21 | * responses:
22 | * '200':
23 | * description: Info
24 | */
25 | app.get("/rest/xml/example1", (req, res) => {
26 | const xml =
27 | `
28 |
29 |
30 | To
31 | From
32 | Note heading
33 | Note body
34 |
35 | `
36 | let body = ""
37 | let note = new XmlDocument(xml)
38 | note.eachChild((item) => {
39 | body += item.val + ''
40 | })
41 | return res.type("text/html").status(200).send(body)
42 | })
43 | return app
44 | }
--------------------------------------------------------------------------------
/srv/routes/user.js:
--------------------------------------------------------------------------------
1 | import * as utils from "../utils/general.js"
2 | import * as locale from "../utils/locale.js"
3 |
4 | export function load (app) {
5 | /**
6 | * @swagger
7 | *
8 | * /rest/user:
9 | * get:
10 | * summary: User/Member Information
11 | * tags:
12 | * - User
13 | * responses:
14 | * '200':
15 | * description: User Details
16 | */
17 | app.get('/rest/user', async (req, res) => {
18 |
19 | try {
20 | let body = JSON.stringify({
21 | "session": [{
22 | "UserName": utils.getSafe(() => req.user.id, ''),
23 | "familyName": utils.getSafe(() => req.user.attr.familyName, ''),
24 | "givenName": utils.getSafe(() => req.user.attr.givenName, ''),
25 | "emails": utils.getSafe(() => req.user.emails, ''),
26 | "Language": locale.getLocaleReq(req)
27 | }]
28 | });
29 | return res.type("application/json").status(200).send(body)
30 | } catch (err) {
31 | app.log.error(err)
32 | return res.type("text/plain").status(500).send(`ERROR: ${err.toString()}`)
33 | }
34 | })
35 | }
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/Component.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | sap.ui.define([
4 | "sap/sample/common/Component"
5 | ], function (UIComponent) {
6 | "use strict";
7 |
8 | return UIComponent.extend("sap.sample.exerciseAsync.Component", {
9 |
10 | metadata: {
11 | manifest: "json"
12 | },
13 |
14 | createContent: function () {
15 | // create root view
16 | var oView = sap.ui.view({
17 | id: "App",
18 | viewName: `sap.sample.exerciseAsync.view.App`,
19 | type: "XML",
20 | async: true,
21 | viewData: {
22 | component: this
23 | }
24 | })
25 | return oView
26 | },
27 |
28 | /**
29 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
30 | * @public
31 | * @override
32 | */
33 | init: function () {
34 | // Chat Model
35 | var oModel = this.getModel("chatModel")
36 | oModel.setData({
37 | chat: "",
38 | message: ""
39 | })
40 | this.superInit()
41 | }
42 | })
43 | })
--------------------------------------------------------------------------------
/app/resources/multiply/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Multiply Exercise
8 |
23 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | exerciseAsync
8 |
23 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/resources/exerciseChat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Exercise: Chat
8 |
23 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/srv/routes/multiply.js:
--------------------------------------------------------------------------------
1 | export function load(app) {
2 |
3 | /**
4 | * @swagger
5 | *
6 | * /rest/multiply/{num1}/{num2}:
7 | * get:
8 | * summary: Multiply Two Numbers
9 | * tags:
10 | * - multiply
11 | * parameters:
12 | * - name: num1
13 | * in: path
14 | * description: Number 1
15 | * required: false
16 | * schema:
17 | * type: integer
18 | * - name: num2
19 | * in: path
20 | * description: Number 2
21 | * required: false
22 | * schema:
23 | * type: integer
24 | * responses:
25 | * '200':
26 | * description: Results
27 | */
28 | app.get('/rest/multiply/:num1?/:num2?', async (req, res) => {
29 |
30 | let body = ""
31 | let num1 = 0
32 | if (!isNaN(parseInt(req.params.num1))) {
33 | num1 = parseInt(req.params.num1)
34 | }
35 | let num2 = 0
36 | if (!isNaN(parseInt(req.params.num2))) {
37 | num2 = parseInt(req.params.num2)
38 | }
39 | let answer
40 |
41 | answer = num1 * num2
42 |
43 | body = answer.toString();
44 | return res.type("application/json").status(200).send(body)
45 | })
46 |
47 | return app
48 | }
--------------------------------------------------------------------------------
/app/resources/exerciseChat/Component.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /*eslint-env es6 */
3 | sap.ui.define([
4 | "sap/sample/common/Component"
5 | ], function (UIComponent) {
6 | "use strict";
7 |
8 | return UIComponent.extend("sap.sample.exerciseChat.Component", {
9 |
10 | metadata: {
11 | manifest: "json"
12 | },
13 |
14 | createContent: function () {
15 | // create root view
16 | var oView = sap.ui.view({
17 | id: "App",
18 | viewName: `sap.sample.exerciseChat.view.App`,
19 | type: "XML",
20 | async: true,
21 | viewData: {
22 | component: this
23 | }
24 | })
25 | return oView
26 | },
27 |
28 | /**
29 | * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
30 | * @public
31 | * @override
32 | */
33 | init: function () {
34 | // Chat Model
35 | let oModel = this.getModel("chatModel")
36 | let names = ["Student1", "Student2", "Student3", "Student4", "Student5", "Student6"]
37 | oModel.setData({
38 | user: names[Math.floor(names.length * Math.random())],
39 | chat: "",
40 | message: ""
41 | })
42 | this.superInit()
43 | }
44 | })
45 | })
--------------------------------------------------------------------------------
/app/browse/fiori-service.cds:
--------------------------------------------------------------------------------
1 | using { CatalogService } from '../../srv/cat-service.cds';
2 |
3 | ////////////////////////////////////////////////////////////////////////////
4 | //
5 | // Books Object Page
6 | //
7 | annotate CatalogService.Books with @(UI : {
8 | HeaderInfo: {
9 | TypeName : 'Book',
10 | TypeNamePlural: 'Books',
11 | Description : {Value : author}
12 | },
13 | HeaderFacets: [{
14 | $Type : 'UI.ReferenceFacet',
15 | Label : '{i18n>Description}',
16 | Target: '@UI.FieldGroup#Descr'
17 | }, ],
18 | Facets: [{
19 | $Type : 'UI.ReferenceFacet',
20 | Label : '{i18n>Details}',
21 | Target: '@UI.FieldGroup#Price'
22 | }, ],
23 | FieldGroup #Descr: {Data : [{Value : descr}, ]},
24 | FieldGroup #Price: {Data : [
25 | {Value: price},
26 | {
27 | Value: currency.symbol,
28 | Label: '{i18n>Currency}'
29 | },
30 | ]},
31 | });
32 |
33 | ////////////////////////////////////////////////////////////////////////////
34 | //
35 | // Books List Page
36 | //
37 | annotate CatalogService.Books with @(UI : {
38 | SelectionFields: [
39 | ID,
40 | price,
41 | currency_code
42 | ],
43 | LineItem: [
44 | {
45 | Value: ID,
46 | Label: '{i18n>Title}'
47 | },
48 | {
49 | Value: author,
50 | Label: '{i18n>Author}'
51 | },
52 | {Value: genre.name},
53 | {Value: price},
54 | {Value: currency.symbol},
55 | ]
56 | });
57 |
--------------------------------------------------------------------------------
/srv/oo/ooTutorial3.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-async-promise-executor */
2 | export default class {
3 | #flight
4 | #title
5 | #loading
6 |
7 | constructor(title) {
8 | this.#loading = true
9 | this.#title = title
10 | }
11 |
12 | #loadFlight(title) {
13 | return new Promise(async (resolve, reject) => {
14 | try {
15 | let dbQuery = SELECT
16 | .from(cds.entities.Books)
17 | .where({ title: title })
18 | resolve(await cds.run(dbQuery))
19 | } catch (err) {
20 | let error = {}
21 | error.message = "Invalid Book"
22 | error.title = title
23 | error.details = err
24 | reject(error)
25 | }
26 | })
27 | }
28 |
29 | async #waitForLoad() {
30 | this.#flight = await this.#loadFlight(this.#title)
31 | this.#loading = false
32 | }
33 |
34 | async getBookDetails() {
35 | if(this.#loading){await this.#waitForLoad()}
36 | return this.#flight
37 | }
38 |
39 | async calculateBookPrice() {
40 | if(this.#loading){await this.#waitForLoad()}
41 | let price
42 | let results = this.#flight
43 | switch (results[0].genre_ID) {
44 | case 14: //Science Fiction
45 | price = results[0].price * 1 + 40
46 | break
47 | case 11: //Drama
48 | price = results[0].price * 1 + 25
49 | break
50 | default:
51 | price = results[0].price * 1 + 10
52 | break
53 | }
54 | return { "price": price, "currency": results[0].currency_code }
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/srv/utils/exampleTOC.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | let tableOfContents =
3 |
4 | "
" +
5 | "Main Testing Entry Points " +
6 | "/apiJS/api-docs - Swagger UI" +
7 | "/ - Launchpad" +
8 |
9 | "" +
10 | "Overall Node.js Examples Table of Contents " +
11 | "/rest/ex2/toc - Node.js Exercises #2" +
12 | "/rest/excAsync - Node.js Asynchronous Non-Blocking I/O Examples" +
13 | "/rest/JavaScriptBasics - Node.js JavaScript Basics" +
14 | "/rest/textBundle/toc - Node.js Text Bundles and Language Processing Examples" +
15 | "/rest/chat - Node.js Web Socket Chat Server Example" +
16 | "/rest/excel - Node.js Excel Upload and Download Examples" +
17 | "/rest/xml - Node.js XML Parsing Examples" +
18 | "/rest/zip - Node.js ZIP archive Examples" +
19 | "/rest/oo - Object Oriented Examples" +
20 | "/rest/os - OS Level Examples" +
21 | "/rest/await - Node.js Async/Await" +
22 | "/rest/es6 - Node.js ES6 & ES2017 Examples" +
23 | "/rest/es2018 - Node.js ES2018 Examples" +
24 | "/rest/multiply - Multiply" +
25 | "/rest/outboundTest - Outbound HTTP"
26 |
27 | return tableOfContents
28 | }
--------------------------------------------------------------------------------
/srv/oo/ooTutorial2.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-async-promise-executor */
2 | export default class {
3 | static async getBookDetails(title) {
4 | return new Promise(async (resolve, reject) => {
5 | try {
6 | let dbQuery = SELECT
7 | .from(cds.entities.Books)
8 | .where({title: title})
9 | resolve(await cds.run(dbQuery))
10 | } catch (err) {
11 | let error = {}
12 | error.message = "Invalid Book"
13 | error.title = title
14 | error.details = err
15 | reject(error)
16 | }
17 | })
18 | }
19 |
20 | static async calculateBookPrice(title) {
21 | return new Promise(async (resolve, reject) => {
22 | try {
23 | let dbQuery = SELECT
24 | .from(cds.entities.Books)
25 | .where({title: title})
26 | .limit(1)
27 | const results = await cds.run(dbQuery)
28 | let price
29 | switch (results[0].genre_ID) {
30 | case 14: //Science Fiction
31 | price = results[0].price * 1 + 40
32 | break
33 | case 11: //Drama
34 | price = results[0].price * 1 + 25
35 | break
36 | default:
37 | price = results[0].price * 1 + 10
38 | break
39 | }
40 | resolve({"price": price, "currency": results[0].currency_code })
41 | } catch (err) {
42 | let error = {}
43 | error.message = "Invalid Book"
44 | error.title = title
45 | error.details = err
46 | reject(error)
47 | }
48 | })
49 | }
50 | }
--------------------------------------------------------------------------------
/REUSE.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | SPDX-PackageName = "cloud-cap-with-javascript-basics"
3 | SPDX-PackageSupplier = "thomas.jung@sap.com"
4 | SPDX-PackageDownloadLocation = "https://github.com/sap-samples/cloud-cap-with-javascript-basics"
5 | SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
6 |
7 | [[annotations]]
8 | path = "**"
9 | precedence = "aggregate"
10 | SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and cloud-cap-with-javascript-basics contributors"
11 | SPDX-License-Identifier = "Apache-2.0"
12 |
--------------------------------------------------------------------------------
/db/data/sap.capire.bookshop-Books_texts.csv:
--------------------------------------------------------------------------------
1 | ID_texts;ID;locale;title;descr
2 | 52eee553-266d-4fdd-a5ca-909910e76ae4;201;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
3 | 54e58142-f06e-49c1-a51d-138f86cea34e;201;fr;Les Hauts de Hurlevent;Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal.
4 | bbbf8a88-797d-4790-af1c-1cc857718ee0;207;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
5 | a90d4378-1a3e-48e7-b60b-5670e78807e1;252;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SAP Cloud Application Programming Model with JavaScript Basics
2 |
3 | [](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-with-javascript-basics)
4 |
5 | ## Description
6 |
7 | This is an SAP Cloud Application Programming Model project that demonstrates how to integrate and use standard JavaScript within your CAP application. It also teaches basic JavaScript techniques and language elements that will be helpful in CAP development. We also see how to add new Express middleware and routes to a single CAP service endpoint.
8 |
9 | ## Requirements
10 |
11 | This sample is based upon the SAP Cloud Application Programming version 7.x and higher.
12 |
13 | ## Download and Installation
14 |
15 | Project can be cloned, install dependencies via `npm install`, and then ran using `npm start` from the root of the project. The default CAP test page will be accessible but also contain links to the applications and testing endpoints of this project.
16 |
17 | ## Known Issues
18 |
19 | No known issues
20 |
21 | ## How to obtain support
22 |
23 | [Create an issue](https://github.com/SAP-samples/cloud-cap-with-javascript-basics/issues) in this repository if you find a bug or have questions about the content.
24 |
25 | For additional support, [ask a question in SAP Community](https://answers.sap.com/questions/ask.html).
26 |
27 | ## Contributing
28 |
29 | If you wish to contribute code, offer fixes or improvements, please send a pull request. Due to legal reasons, contributors will be asked to accept a DCO when they create the first pull request to this project. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/).
30 |
31 | ## License
32 |
33 | Copyright (c) 2024 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file.
34 |
--------------------------------------------------------------------------------
/srv/routes/os.js:
--------------------------------------------------------------------------------
1 | import os from 'os'
2 | import toc from '../utils/exampleTOC.js'
3 | import child_process from 'child_process'
4 | global.child = null
5 |
6 | export function load(app){
7 |
8 | //Hello Router
9 | app.get("/rest/os", (req, res) => {
10 | var output =
11 | `OS level Examples
12 | /osInfo
13 | /whoami ` +
14 | toc()
15 | res.type("text/html").status(200).send(output)
16 | })
17 |
18 | /**
19 | * @swagger
20 | *
21 | * /rest/os/osInfo:
22 | * get:
23 | * summary: Simple call to the Operating System
24 | * tags:
25 | * - os
26 | * responses:
27 | * '200':
28 | * description: Output
29 | */
30 | app.get("/rest/os/osInfo", (req, res) => {
31 | let output = {}
32 |
33 | output.tmpdir = os.tmpdir()
34 | output.endianness = os.endianness()
35 | output.hostname = os.hostname()
36 | output.type = os.type()
37 | output.platform = os.platform()
38 | output.arch = os.arch()
39 | output.release = os.release()
40 | output.uptime = os.uptime()
41 | output.loadavg = os.loadavg()
42 | output.totalmem = os.totalmem()
43 | output.freemem = os.freemem()
44 | output.cpus = os.cpus()
45 | output.networkInterfaces = os.networkInterfaces()
46 |
47 | var result = JSON.stringify(output)
48 | res.type("application/json").status(200).send(result)
49 | })
50 |
51 | /**
52 | * @swagger
53 | *
54 | * /rest/os/whoami:
55 | * get:
56 | * summary: Return OS level User
57 | * tags:
58 | * - os
59 | * responses:
60 | * '200':
61 | * description: Output
62 | */
63 | app.get("/rest/os/osUser", (req, res, next) => {
64 | var exec = child_process.exec
65 | exec("whoami", (e, stdout) => {
66 | if (e) {
67 | app.log.error(e)
68 | let error = new cds.error(e)
69 | return next(error)
70 | } else {
71 | res.type("text/plain").status(200).send(stdout)
72 | }
73 | })
74 | })
75 |
76 | return app
77 | }
--------------------------------------------------------------------------------
/srv/server/expressSecurity.js:
--------------------------------------------------------------------------------
1 | import helmet from 'helmet'
2 | //import basic_auth from '@sap/cds/lib/auth/basic-auth.js'
3 | export default function (app) {
4 | app.log(`Add Helmet`)
5 | app.use(helmet())
6 | app.use(helmet.contentSecurityPolicy({
7 | directives: {
8 | defaultSrc: ["'self'", "*.hana.ondemand.com", "ui5.sap.com", "unpkg.com"],
9 | styleSrc: ["'self'", "*.hana.ondemand.com", "ui5.sap.com", "unpkg.com", "'unsafe-inline'"],
10 | scriptSrc: ["'self'", "*.hana.ondemand.com", "ui5.sap.com", "unpkg.com", "'unsafe-inline'", "'unsafe-eval'", "cdnjs.cloudflare.com"],
11 | imgSrc: ["'self'", "*.hana.ondemand.com", "ui5.sap.com", "unpkg.com", "*.loc.gov", "data:"]
12 | }
13 | }))
14 | // Sets "Referrer-Policy: no-referrer".
15 | app.use(helmet.referrerPolicy({ policy: "no-referrer" }))
16 |
17 | //Add back in for mocked testing of user authentication and authorization
18 | /* const admin = ['cds.Subscriber', 'admin']
19 | const builder = ['cds.ExtensionDeveloper', 'cds.UIFlexDeveloper']
20 | app.use(basic_auth(
21 | {
22 | kind: 'basic-auth',
23 | impl: 'node_modules\\@sap\\cds\\lib\\auth\\basic-auth.js',
24 | users: {
25 | alice: { tenant: 't1', roles: [...admin], "attr": {familyName: "Mock", givenName: "Alice"}},
26 | bob: { tenant: 't1', roles: [...builder] },
27 | carol: { tenant: 't1', roles: [...admin, ...builder] },
28 | dave: { tenant: 't1', roles: [...admin], features: [] },
29 | erin: { tenant: 't2', roles: [...admin, ...builder] },
30 | fred: { tenant: 't2', features: ['isbn'] },
31 | me: { tenant: 't1', features: ['*'] },
32 | yves: { roles: ['internal-user'] },
33 | '*': true
34 | },
35 | tenants: {
36 | t1: { features: ['isbn'], }, // tenant-specific features
37 | t2: { features: '*', },
38 | }
39 | }
40 | )) */
41 | }
--------------------------------------------------------------------------------
/srv/utils/general.js:
--------------------------------------------------------------------------------
1 | export function getSafe(fn, defaultVal) {
2 | try {
3 | return fn()
4 | } catch (e) {
5 | return defaultVal
6 | }
7 | }
8 |
9 | export function callback(error, res, message) {
10 | if (error) {
11 | res.writeHead(500, {
12 | 'Content-Type': 'application/json'
13 | })
14 | res.end(JSON.stringify({
15 | message: message
16 | }))
17 | } else {
18 | res.writeHead(200, {
19 | 'Content-Type': 'application/json'
20 | })
21 | res.end(JSON.stringify({
22 | message: message
23 | }))
24 | }
25 | }
26 |
27 | export function isAlphaNumeric(str) {
28 | let code, i, len
29 | for (i = 0, len = str.length; i < len; i++) {
30 | code = str.charCodeAt(i)
31 | if (!(code > 47 && code < 58) && // numeric (0-9)
32 | !(code > 64 && code < 91) && // upper alpha (A-Z)
33 | !(code > 96 && code < 123)) { // lower alpha (a-z)
34 | return false
35 | }
36 | }
37 | return true
38 | }
39 |
40 | export function isAlphaNumericAndSpace(str) {
41 | let res = str.match(/^[a-z\d\-_\s]+$/i)
42 | if (res) {
43 | return true
44 | } else {
45 | return false
46 | }
47 | }
48 |
49 | export function isValidDate(date) {
50 | console.log("date" + date)
51 | var timestamp = Date.parse(date)
52 | console.log("timsestamp" + timestamp)
53 | if (isNaN(timestamp) === true) {
54 | return false
55 | }
56 | return true
57 | }
58 |
59 | export function isEmpty(val) {
60 | if (val === undefined)
61 | return true
62 | if (typeof (val) == 'function' || typeof (val) == 'number' || typeof (val) == 'boolean' || Object.prototype.toString.call(val) ===
63 | '[object Date]')
64 | return false
65 | if (val == null || val.length === 0) // null or 0 length array
66 | return true
67 | if (typeof (val) == "object") {
68 | // empty object
69 | var r = true
70 | // eslint-disable-next-line no-unused-vars
71 | for (let f in val)
72 | r = false
73 | return r
74 | }
75 | return false
76 | }
--------------------------------------------------------------------------------
/srv/routes/smtp.js:
--------------------------------------------------------------------------------
1 | import xsenv from '@sap/xsenv'
2 |
3 | export function load(app) {
4 | /**
5 | * @swagger
6 | *
7 | * /rest/smtp:
8 | * get:
9 | * summary: SMTP example via nodemailer (requires external SMTP service setup)
10 | * tags:
11 | * - smtp
12 | * responses:
13 | * '200':
14 | * description: Sent mail info
15 | */
16 | app.get("/rest/smtp", (req, res, next) => {
17 | let options = {}
18 | //Add SMTP
19 | try {
20 | options = Object.assign(options, xsenv.getServices({
21 | mail: {
22 | "name": "dat260.smtp"
23 | }
24 | }))
25 |
26 | const nodemailer = require("nodemailer")
27 | // create reusable transporter object using the default SMTP transport
28 | app.logger.info(JSON.stringify(options.mail))
29 | let transporter = nodemailer.createTransport(options.mail)
30 |
31 | // setup email data with unicode symbols
32 | let mailOptions = {
33 | from: "\"DAT260\" ", // list of receivers
35 | subject: "Mail Test from Pure Node.js using NodeMailer", // Subject line
36 | text: "The body of the mail from Pure Node.js using NodeMailer" // plain text body
37 | // html: 'Hello world? ' // html body
38 | }
39 |
40 | // send mail with defined transport object
41 | transporter.sendMail(mailOptions, (error, info) => {
42 | if (error) {
43 | return app.log.error(error)
44 | }
45 | console.log("Message sent: %s", info.messageId)
46 | console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info))
47 | let output = `Preview URL: ${nodemailer.getTestMessageUrl(info)} `
48 | return res.type("text/html").status(200).send(output)
49 | })
50 |
51 | } catch (e) {
52 | app.log.error(e)
53 | let error = new cds.error(e)
54 | return next(error)
55 | }
56 | })
57 |
58 | return app
59 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cap-with-javascript-basics",
3 | "version": "1.1.0",
4 | "description": "A simple CAP project.",
5 | "repository": "https://github.com/SAP-samples/cloud-cap-with-javascript-basics",
6 | "license": "Apache-2.0",
7 | "private": false,
8 | "type": "module",
9 | "engines": {
10 | "node": ">=20"
11 | },
12 | "dependencies": {
13 | "@cap-js-community/odata-v2-adapter": "^1",
14 | "@cap-js/graphql": "^0.9.0",
15 | "@cloudnative/health-connect": "^2.1.0",
16 | "@json2csv/node": "^7.0.4",
17 | "@sap/cds": "^7",
18 | "@sap/cds-common-content": "^1",
19 | "@sap/cds-fiori": "^1.2.2",
20 | "@sap/textbundle": "^4.3.0",
21 | "@sap/xsenv": "^4.2.0",
22 | "accept-language-parser": "^1.5.0",
23 | "async": "^3.2.5",
24 | "body-parser": "^1.20.2",
25 | "cds-swagger-ui-express": "^0.8.0",
26 | "cors": "^2.8.5",
27 | "easy-table": "^1.2.0",
28 | "event-loop-lag": "^1.4.0",
29 | "express": "^4",
30 | "express-status-monitor": "^1.3.4",
31 | "glob": "^10.3.10",
32 | "helmet": "^7.1.0",
33 | "node-xlsx": "^0.23.0",
34 | "node-zip": "^1.1.1",
35 | "overload-protection": "^1.2.3",
36 | "swagger-jsdoc": "^6.2.8",
37 | "then-request": "^6.0.2",
38 | "upath": "^2.0.1",
39 | "ws": "^8.16.0",
40 | "xmldoc": "^1.3.0"
41 | },
42 | "devDependencies": {
43 | "@cap-js/cds-typer": "^0.15.0",
44 | "@cap-js/sqlite": "^1",
45 | "@sap/cds-dk": "^7",
46 | "@sap/eslint-plugin-cds": "^2",
47 | "eslint": "^8"
48 | },
49 | "scripts": {
50 | "watch": "cds watch --profile development",
51 | "start": "cds-serve",
52 | "types": "npx @cap-js/cds-typer ./db/schema.cds --outputDirectory ./@cds-models"
53 | },
54 | "cds": {
55 | "requires": {
56 | "auth": "mocked"
57 | },
58 | "log": {
59 | "levels": {
60 | "sqlite": "debug",
61 | "cds": "info",
62 | "nodejs": "info"
63 | }
64 | },
65 | "fiori": {
66 | "preview": {
67 | "ui5": {
68 | "version": "1.120.4"
69 | }
70 | }
71 | },
72 | "cov2ap": {
73 | "plugin": true
74 | },
75 | "protocols": {
76 | "graphql": {
77 | "path": "/graphql",
78 | "impl": "@cap-js/graphql"
79 | }
80 | }
81 | },
82 | "imports": {
83 | "#cds-models/*": "./@cds-models/*/index.js"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/resources/multiply/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | /*eslint no-console: 0, no-unused-vars: 0, no-use-before-define: 0, no-redeclare: 0, no-undef: 0, no-sequences: 0, no-unused-expressions: 0*/
2 | /*eslint-env es6 */
3 | //To use a javascript controller its name must end with .controller.js
4 | sap.ui.define([
5 | "sap/sample/common/controller/BaseController",
6 | "sap/ui/model/json/JSONModel"
7 | ], function (BaseController, Model) {
8 | "use strict";
9 | return BaseController.extend("sap.sample.multiply.controller.App", {
10 |
11 | onInit: function () {
12 | let model = new Model({})
13 | this.getView().setModel(model)
14 | },
15 |
16 | onLiveChange: function (oEvent) {
17 | let view = this.getView()
18 | let result = view.getModel().getData()
19 | let controller = this.getView().getController()
20 | let valSend
21 | if (oEvent.getParameters().id.includes("val1")) {
22 | valSend = result.val2
23 | } else {
24 | valSend = result.val1
25 | }
26 | if (valSend === undefined) {
27 | valSend = 0
28 | }
29 | let aUrl = "/rest/multiply/" +
30 | + encodeURIComponent(oEvent.getParameters().newValue) +
31 | "/" + encodeURIComponent(valSend)
32 | jQuery.ajax({
33 | url: aUrl,
34 | method: "GET",
35 | dataType: "json",
36 | success: (myTxt) => {
37 | controller.onCompleteMultiply(myTxt, view)
38 | },
39 | error: controller.onErrorCall
40 | })
41 | },
42 |
43 | onCompleteMultiply: function (myTxt, view) {
44 | var oResult = view.byId("result")
45 | if (myTxt === undefined) {
46 | oResult.setText(0)
47 | } else {
48 | sap.ui.require(["sap/ui/core/format/NumberFormat"], (NumberFormat) => {
49 | var oNumberFormat = NumberFormat.getIntegerInstance({
50 | maxFractionDigits: 12,
51 | minFractionDigits: 0,
52 | groupingEnabled: true
53 | });
54 | oResult.setText(oNumberFormat.format(myTxt));
55 | })
56 | }
57 | }
58 |
59 | })
60 | })
--------------------------------------------------------------------------------
/app/resources/exerciseChat/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | /*eslint no-console: 0, no-unused-vars: 0, no-use-before-define: 0, no-redeclare: 0, no-undef: 0, no-sequences: 0, no-unused-expressions: 0*/
2 | /*eslint-env es6 */
3 | //To use a javascript controller its name must end with .controller.js
4 | sap.ui.define([
5 | "sap/sample/common/controller/BaseController",
6 | "sap/ui/model/json/JSONModel",
7 | "sap/ui/core/ws/WebSocket",
8 | "sap/m/MessageToast"
9 | ], function (BaseController, JSONModel, WebSocket, MessageToast) {
10 | "use strict";
11 | var connection = new WebSocket("/rest/chatServer")
12 |
13 | return BaseController.extend("sap.sample.exerciseChat.controller.App", {
14 |
15 | onInit: function () {
16 | // connection opened
17 | connection.attachOpen((oControlEvent) => {
18 | MessageToast.show("connection opened")
19 | })
20 |
21 | // server messages
22 | connection.attachMessage((oControlEvent) => {
23 | var oModel = this.getModel("chatModel")
24 | var eventData = oControlEvent.getParameter("data")
25 | var result = oModel.getData()
26 |
27 | var data = jQuery.parseJSON(eventData)
28 | var msg = data.user + ": " + data.text,
29 | lastInfo = result.chat
30 |
31 | if (lastInfo.length > 0) {
32 | lastInfo += "\r\n"
33 | }
34 | oModel.setData({
35 | chat: lastInfo + msg
36 | }, true)
37 | }, this)
38 |
39 | // error handling
40 | connection.attachError((oControlEvent) => {
41 | MessageToast.show("Websocket connection error")
42 | })
43 |
44 | // onConnectionClose
45 | connection.attachClose((oControlEvent) => {
46 | MessageToast.show("Websocket connection closed")
47 | })
48 | },
49 |
50 | // send message
51 | sendMsg: function () {
52 | let oModel = this.getOwnerComponent().getModel("chatModel")
53 | let result = oModel.getData()
54 | let msg = result.message
55 | if (msg.length > 0) {
56 | connection.send(JSON.stringify({
57 | user: result.user,
58 | text: result.message
59 | }))
60 | oModel.setData({
61 | message: ""
62 | }, true)
63 | }
64 | }
65 | })
66 | })
--------------------------------------------------------------------------------
/app/resources/multiply/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.33.0",
3 | "sap.app": {
4 | "id": "sap.sample.multiply",
5 | "type": "application",
6 | "i18n": "i18n/i18n.properties",
7 | "applicationVersion": {
8 | "version": "1.0.0"
9 | },
10 | "title": "{{appTitle}}",
11 | "description": "{{appDescription}}",
12 | "sourceTemplate": {
13 | "id": "html5moduletemplates.basicSAPUI5ApplicationProjectModule",
14 | "version": "1.40.12"
15 | }
16 | },
17 |
18 | "sap.ui": {
19 | "technology": "UI5",
20 | "icons": {
21 | "icon": "../common/images/favicon.ico",
22 | "favIcon": "../common/images/favicon.ico",
23 | "phone": "",
24 | "phone@2": "",
25 | "tablet": "",
26 | "tablet@2": ""
27 | },
28 | "deviceTypes": {
29 | "desktop": true,
30 | "tablet": true,
31 | "phone": true
32 | }
33 | },
34 |
35 | "sap.ui5": {
36 | "resourceRoots": {
37 | "sap.sample.common": "../common"
38 | },
39 | "flexEnabled": true,
40 | "dependencies": {
41 | "minUI5Version": "1.91.0",
42 | "libs": {
43 | "sap.ui.core": {},
44 | "sap.m": {},
45 | "sap.ui.layout": {}
46 | }
47 | },
48 | "contentDensities": {
49 | "compact": true,
50 | "cozy": true
51 | },
52 | "models": {
53 | "config": {
54 | "type": "sap.ui.model.json.JSONModel"
55 | },
56 | "i18n": {
57 | "type": "sap.ui.model.resource.ResourceModel",
58 | "settings": {
59 | "bundleName": "sap.sample.multiply.i18n.i18n"
60 | }
61 | },
62 | "i18nReuse": {
63 | "type": "sap.ui.model.resource.ResourceModel",
64 | "settings": {
65 | "bundleUrl": "/i18n/messages.properties"
66 | }
67 | }
68 | },
69 | "resources": {
70 | "css": [{
71 | "uri": "../common/css/style.css"
72 | }]
73 | },
74 | "routing": {
75 | "config": {
76 | "routerClass": "sap.m.routing.Router",
77 | "viewType": "XML",
78 | "async": true,
79 | "viewPath": "sap.sample.multiply.view",
80 | "controlAggregation": "pages",
81 | "controlId": "app",
82 | "clearControlAggregation": false
83 | },
84 | "routes": [{
85 | "name": "RouteApp",
86 | "pattern": "RouteApp",
87 | "target": ["TargetApp"]
88 | }],
89 | "targets": {
90 | "TargetApp": {
91 | "viewType": "XML",
92 | "transition": "slide",
93 | "clearControlAggregation": false,
94 | "viewId": "App",
95 | "viewName": "App"
96 | }
97 | }
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/app/resources/exerciseChat/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.33.0",
3 | "sap.app": {
4 | "id": "sap.sample.exerciseChat",
5 | "type": "application",
6 | "i18n": "i18n/i18n.properties",
7 | "applicationVersion": {
8 | "version": "1.0.0"
9 | },
10 | "title": "{{appTitle}}",
11 | "description": "{{appDescription}}",
12 | "sourceTemplate": {
13 | "id": "html5moduletemplates.basicSAPUI5ApplicationProjectModule",
14 | "version": "1.40.12"
15 | }
16 | },
17 |
18 | "sap.ui": {
19 | "technology": "UI5",
20 | "icons": {
21 | "icon": "../common/images/favicon.ico",
22 | "favIcon": "../common/images/favicon.ico",
23 | "phone": "",
24 | "phone@2": "",
25 | "tablet": "",
26 | "tablet@2": ""
27 | },
28 | "deviceTypes": {
29 | "desktop": true,
30 | "tablet": true,
31 | "phone": true
32 | }
33 | },
34 |
35 | "sap.ui5": {
36 | "resourceRoots": {
37 | "sap.sample.common": "../common"
38 | },
39 | "flexEnabled": true,
40 | "dependencies": {
41 | "minUI5Version": "1.91.0",
42 | "libs": {
43 | "sap.ui.core": {},
44 | "sap.m": {},
45 | "sap.ui.layout": {}
46 | }
47 | },
48 | "contentDensities": {
49 | "compact": true,
50 | "cozy": true
51 | },
52 | "models": {
53 | "chatModel": {
54 | "type": "sap.ui.model.json.JSONModel",
55 | "settings": {
56 | "defaultBindingMode": "TwoWay"
57 | }
58 | },
59 | "config": {
60 | "type": "sap.ui.model.json.JSONModel"
61 | },
62 | "i18n": {
63 | "type": "sap.ui.model.resource.ResourceModel",
64 | "settings": {
65 | "bundleName": "sap.sample.exerciseChat.i18n.i18n"
66 | }
67 | },
68 | "i18nReuse": {
69 | "type": "sap.ui.model.resource.ResourceModel",
70 | "settings": {
71 | "bundleUrl": "/i18n/messages.properties"
72 | }
73 | }
74 | },
75 | "resources": {
76 | "css": [{
77 | "uri": "../common/css/style.css"
78 | }]
79 | },
80 | "routing": {
81 | "config": {
82 | "routerClass": "sap.m.routing.Router",
83 | "viewType": "XML",
84 | "async": true,
85 | "viewPath": "sap.sample.exerciseChat.view",
86 | "controlAggregation": "pages",
87 | "controlId": "app",
88 | "clearControlAggregation": false
89 | },
90 | "routes": [{
91 | "name": "RouteApp",
92 | "pattern": "RouteApp",
93 | "target": ["TargetApp"]
94 | }],
95 | "targets": {
96 | "TargetApp": {
97 | "viewType": "XML",
98 | "transition": "slide",
99 | "clearControlAggregation": false,
100 | "viewId": "App",
101 | "viewName": "App"
102 | }
103 | }
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.33.0",
3 | "sap.app": {
4 | "id": "sap.sample.exerciseAsync",
5 | "type": "application",
6 | "i18n": "i18n/i18n.properties",
7 | "applicationVersion": {
8 | "version": "1.0.0"
9 | },
10 | "title": "{{appTitle}}",
11 | "description": "{{appDescription}}",
12 | "sourceTemplate": {
13 | "id": "html5moduletemplates.basicSAPUI5ApplicationProjectModule",
14 | "version": "1.40.12"
15 | }
16 | },
17 |
18 | "sap.ui": {
19 | "technology": "UI5",
20 | "icons": {
21 | "icon": "../common/images/favicon.ico",
22 | "favIcon": "../common/images/favicon.ico",
23 | "phone": "",
24 | "phone@2": "",
25 | "tablet": "",
26 | "tablet@2": ""
27 | },
28 | "deviceTypes": {
29 | "desktop": true,
30 | "tablet": true,
31 | "phone": true
32 | }
33 | },
34 |
35 | "sap.ui5": {
36 | "resourceRoots": {
37 | "sap.sample.common": "../common"
38 | },
39 | "flexEnabled": true,
40 | "dependencies": {
41 | "minUI5Version": "1.91.0",
42 | "libs": {
43 | "sap.ui.core": {},
44 | "sap.m": {},
45 | "sap.ui.layout": {}
46 | }
47 | },
48 | "contentDensities": {
49 | "compact": true,
50 | "cozy": true
51 | },
52 | "models": {
53 | "chatModel": {
54 | "type": "sap.ui.model.json.JSONModel",
55 | "settings": {
56 | "defaultBindingMode": "TwoWay"
57 | }
58 | },
59 | "config": {
60 | "type": "sap.ui.model.json.JSONModel"
61 | },
62 | "i18n": {
63 | "type": "sap.ui.model.resource.ResourceModel",
64 | "settings": {
65 | "bundleName": "sap.sample.exerciseAsync.i18n.i18n"
66 | }
67 | },
68 | "i18nReuse": {
69 | "type": "sap.ui.model.resource.ResourceModel",
70 | "settings": {
71 | "bundleUrl": "/i18n/messages.properties"
72 | }
73 | }
74 | },
75 | "resources": {
76 | "css": [{
77 | "uri": "../common/css/style.css"
78 | }]
79 | },
80 | "routing": {
81 | "config": {
82 | "routerClass": "sap.m.routing.Router",
83 | "viewType": "XML",
84 | "async": true,
85 | "viewPath": "sap.sample.exerciseAsync.view",
86 | "controlAggregation": "pages",
87 | "controlId": "app",
88 | "clearControlAggregation": false
89 | },
90 | "routes": [{
91 | "name": "RouteApp",
92 | "pattern": "RouteApp",
93 | "target": ["TargetApp"]
94 | }],
95 | "targets": {
96 | "TargetApp": {
97 | "viewType": "XML",
98 | "transition": "slide",
99 | "clearControlAggregation": false,
100 | "viewId": "App",
101 | "viewName": "App"
102 | }
103 | }
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/srv/routes/chatServer.js:
--------------------------------------------------------------------------------
1 | import { WebSocketServer } from 'ws'
2 | import toc from '../utils/exampleTOC.js'
3 |
4 | export function load(app, server) {
5 |
6 | app.use('/rest/chat', (req, res) => {
7 | let output =
8 | `Node.js Web Socket Examples
9 | /exerciseChat - Chat Application for Web Socket Example`+
10 | toc()
11 | res.type("text/html").status(200).send(output)
12 | })
13 |
14 | try {
15 | //Create Server
16 | var wss = new WebSocketServer({
17 | noServer: true,
18 | path: "/rest/chatServer"
19 | })
20 |
21 | //Handle Upgrade Event
22 | server.on("upgrade", (request, socket, head) => {
23 | if (request.url === '/rest/chatServer') {
24 | wss.handleUpgrade(request, socket, head, (ws) => {
25 | wss.emit("connection", ws, request)
26 | })
27 | }
28 | })
29 |
30 | //Broadcast function
31 | wss.broadcast = (data) => {
32 | let message = JSON.stringify(data)
33 | wss.clients.forEach((client) => {
34 | try {
35 | client.send(message, (error) => {
36 | if (error !== null && typeof error !== "undefined") {
37 | cds.log('nodejs').error(`Send Error: ${error}`)
38 | }
39 | })
40 | } catch (e) {
41 | cds.log('nodejs').error(`Broadcast Error: ${e}`)
42 | }
43 | })
44 | cds.log('nodejs').info(`Sent: ${message}`)
45 | }
46 |
47 | //Web Socket Error Handler
48 | wss.on("error", (error) => {
49 | cds.log('nodejs').error(`Web Socket Server Error: ${error}`)
50 | })
51 |
52 | //Web Socket Connection
53 | wss.on("connection", (ws) => {
54 | cds.log('nodejs').info("Connected")
55 |
56 | //Web Socket Message Received
57 | ws.on("message", (message) => {
58 | let data = JSON.parse(message)
59 | cds.log('nodejs').info(data)
60 | wss.broadcast(data)
61 | })
62 |
63 | ws.send(JSON.stringify({
64 | user: "Node",
65 | text: "Hello from Node.js Server"
66 | }), (error) => {
67 | if (error !== null && typeof error !== "undefined") {
68 | cds.log('nodejs').error(`Send Error: ${error}`)
69 | }
70 | })
71 | })
72 |
73 |
74 | } catch (e) {
75 | cds.log('nodejs').error(e)
76 | }
77 | return app
78 | }
--------------------------------------------------------------------------------
/srv/routes/excel.js:
--------------------------------------------------------------------------------
1 | import excel from 'node-xlsx'
2 | import bodyParser from 'body-parser'
3 |
4 | export function load (app) {
5 | app.get("/rest/excel", (req, res) => {
6 | var output =
7 | `Excel Examples
8 | /download - Download data in Excel XLSX format`
9 | res.type("text/html").status(200).send(output)
10 | });
11 |
12 | /**
13 | * @swagger
14 | *
15 | * /rest/excel/download:
16 | * get:
17 | * summary: Export PO Items in Excel
18 | * tags:
19 | * - excel
20 | * responses:
21 | * '200':
22 | * description: Contents of PO Items in Excel
23 | * content:
24 | * application/vnd.ms-excel:
25 | * schema:
26 | * type: string
27 | * format: binary
28 | * '500':
29 | * description: General DB Error
30 | */
31 | app.get("/rest/excel/download", async (req, res) => {
32 | try {
33 |
34 | let dbQuery = SELECT
35 | .from(cds.entities.Books)
36 | .limit(10)
37 | const results = await cds.run(dbQuery)
38 | let out = []
39 | for (let item of results) {
40 | out.push([item.ID, item.title, item.stock, item.price, item.currency_code])
41 | }
42 | var result = excel.build([{
43 | name: "Books",
44 | data: out
45 | }])
46 | res.header("Content-Disposition", "attachment; filename=Excel.xlsx")
47 | return res.type("application/vnd.ms-excel").status(200).send(result)
48 | } catch (e) {
49 | app.log.error(e)
50 | return res.type("text/plain").status(500).send(`${e}`)
51 | }
52 | })
53 |
54 | /**
55 | * @swagger
56 | *
57 | * /rest/excel/upload:
58 | * post:
59 | * summary: Upload Excel File and parse
60 | * tags:
61 | * - excel
62 | * requestBody:
63 | * description: Excel File
64 | * required: true
65 | * content:
66 | * application/octet-stream:
67 | * schema:
68 | * type: string
69 | * format: binary
70 | * responses:
71 | * '201':
72 | * description: All data uploaded successfully
73 | * '500':
74 | * description: General Error
75 | */
76 |
77 | let excelParser = bodyParser.raw({
78 | type: 'application/octet-stream'
79 | })
80 | app.post('/rest/excel/upload', excelParser, async (req, res) => {
81 | try {
82 | const workSheetsFromBuffer = excel.parse(req.body)
83 | app.log(workSheetsFromBuffer)
84 | return res.status(201).end(`All Data Imported: ${JSON.stringify(workSheetsFromBuffer)}`)
85 | } catch (err) {
86 | return res.type("text/plain").status(500).send(`ERROR: ${JSON.stringify(err)}`)
87 | }
88 | })
89 |
90 | }
--------------------------------------------------------------------------------
/srv/routes/outboundTest.js:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 |
3 | export function load(app) {
4 | /**
5 | * @swagger
6 | *
7 | * /rest/outboundTest/{search}/{index}:
8 | * get:
9 | * summary: Multiply Two Numbers
10 | * tags:
11 | * - outboundTest
12 | * parameters:
13 | * - name: search
14 | * in: path
15 | * description: Search String
16 | * required: true
17 | * schema:
18 | * type: string
19 | * - name: index
20 | * in: path
21 | * description: Result Index
22 | * required: false
23 | * schema:
24 | * type: integer
25 | * responses:
26 | * '200':
27 | * description: Results
28 | */
29 | app.get('/rest/outboundTest/:search/:index?', async (req, res, next) => {
30 |
31 | let search = req.params.search
32 | let index = 0
33 | if (!isNaN(parseInt(req.params.index))) {
34 | index = parseInt(req.params.index)
35 | }
36 | try {
37 | http.get({
38 | path: `http://www.loc.gov/pictures/search/?fo=json&q=${search}&`,
39 | host: "www.loc.gov",
40 | port: "80",
41 | headers: {
42 | host: "www.loc.gov"
43 | }
44 | },
45 | (response) => {
46 | response.setEncoding("utf8")
47 | let body = ''
48 |
49 | response.on("data", (data) => {
50 | body += data
51 | })
52 |
53 | response.on("end", () => {
54 | let searchDet = JSON.parse(body)
55 | let image = ""
56 | if(searchDet.results[index] && searchDet.results[index].image){
57 | image = searchDet.results[index].image.full
58 | }
59 |
60 | let outBody =
61 | `${encodeURIComponent(index)} Result of ${encodeURIComponent(searchDet.search.hits)}
62 | `
63 | return res.type("text/html").status(200).send(outBody)
64 | })
65 |
66 | response.on("error", (e) => {
67 | app.log.error(e)
68 | let error = new cds.error(e)
69 | return next(error)
70 | })
71 | })
72 | } catch (e) {
73 | app.log.error(e)
74 | let error = new cds.error(e)
75 | return next(error)
76 | }
77 | })
78 |
79 | return app
80 | }
--------------------------------------------------------------------------------
/db/data/sap.capire.bookshop-Books.csv:
--------------------------------------------------------------------------------
1 | ID;title;descr;author_ID;stock;price;currency_code;genre_ID
2 | 201;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";101;12;11.11;GBP;11
3 | 207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
4 | 251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
5 | 252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16
6 | 271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13
7 |
--------------------------------------------------------------------------------
/app/admin-books/fiori-service.cds:
--------------------------------------------------------------------------------
1 | using { AdminService } from '../../srv/admin-service.cds';
2 | using { sap.capire.bookshop } from '../../db/schema';
3 |
4 | ////////////////////////////////////////////////////////////////////////////
5 | //
6 | // Books Object Page
7 | //
8 |
9 | annotate AdminService.Books with @(UI: {
10 | Facets: [
11 | {
12 | $Type : 'UI.ReferenceFacet',
13 | Label : '{i18n>General}',
14 | Target: '@UI.FieldGroup#General'
15 | },
16 | {
17 | $Type : 'UI.ReferenceFacet',
18 | Label : '{i18n>Translations}',
19 | Target: 'texts/@UI.LineItem'
20 | },
21 | {
22 | $Type : 'UI.ReferenceFacet',
23 | Label : '{i18n>Details}',
24 | Target: '@UI.FieldGroup#Details'
25 | },
26 | {
27 | $Type : 'UI.ReferenceFacet',
28 | Label : '{i18n>Admin}',
29 | Target: '@UI.FieldGroup#Admin'
30 | },
31 | ],
32 | FieldGroup #General: {Data: [
33 | {Value: title},
34 | {Value: author_ID},
35 | {Value: genre_ID},
36 | {Value: descr},
37 | ]},
38 | FieldGroup #Details: {Data: [
39 | {Value: stock},
40 | {Value: price},
41 | {
42 | Value: currency_code,
43 | Label: '{i18n>Currency}'
44 | },
45 | ]},
46 | FieldGroup #Admin: {Data: [
47 | {Value: createdBy},
48 | {Value: createdAt},
49 | {Value: modifiedBy},
50 | {Value: modifiedAt}
51 | ]}
52 | });
53 |
54 |
55 | ////////////////////////////////////////////////////////////
56 | //
57 | // Draft for Localized Data
58 | //
59 |
60 | annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
61 | annotate AdminService.Books with @odata.draft.enabled;
62 |
63 | annotate AdminService.Books.texts with @(UI: {
64 | Identification: [{Value: title}],
65 | SelectionFields: [
66 | locale,
67 | title
68 | ],
69 | LineItem: [
70 | {
71 | Value: locale,
72 | Label: 'Locale'
73 | },
74 | {
75 | Value: title,
76 | Label: 'Title'
77 | },
78 | {
79 | Value: descr,
80 | Label: 'Description'
81 | },
82 | ]
83 | });
84 |
85 | annotate AdminService.Books.texts with {
86 | ID @UI.Hidden;
87 | ID_texts @UI.Hidden;
88 | };
89 |
90 | // Add Value Help for Locales
91 | annotate AdminService.Books.texts {
92 | locale @(
93 | ValueList.entity: 'Languages',
94 | Common.ValueListWithFixedValues, //show as drop down, not a dialog
95 | )
96 | };
97 |
98 | // In addition we need to expose Languages through AdminService as a target for ValueList
99 | using {sap} from '@sap/cds/common';
100 |
101 | extend service AdminService {
102 | @readonly entity Languages as projection on sap.common.Languages;
103 | }
104 |
105 | // Workaround for Fiori popup for asking user to enter a new UUID on Create
106 | annotate AdminService.Books with {
107 | ID @Core.Computed;
108 | }
109 |
110 | // Show Genre as drop down, not a dialog
111 | annotate AdminService.Books with {
112 | genre @Common.ValueListWithFixedValues;
113 | }
114 |
--------------------------------------------------------------------------------
/app/appconfig/fioriSandboxConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "services": {
3 | "LaunchPage": {
4 | "adapter": {
5 | "config": {
6 | "catalogs": [],
7 | "groups": [
8 | {
9 | "id": "Bookshop",
10 | "title": "Bookshop",
11 | "isPreset": true,
12 | "isVisible": true,
13 | "isGroupLocked": false,
14 | "tiles": [
15 | {
16 | "id": "BrowseBooks",
17 | "tileType": "sap.ushell.ui.tile.StaticTile",
18 | "properties": {
19 | "title": "Browse Books",
20 | "targetURL": "#Books-display"
21 | }
22 | }
23 | ]
24 | },
25 | {
26 | "id": "Administration",
27 | "title": "Administration",
28 | "isPreset": true,
29 | "isVisible": true,
30 | "isGroupLocked": false,
31 | "tiles": [
32 | {
33 | "id": "ManageBooks",
34 | "tileType": "sap.ushell.ui.tile.StaticTile",
35 | "properties": {
36 | "title": "Manage Books",
37 | "targetURL": "#Books-manage"
38 | }
39 | }
40 | ]
41 | }
42 | ]
43 | }
44 | }
45 | },
46 | "NavTargetResolution": {
47 | "config": {
48 | "enableClientSideTargetResolution": true
49 | }
50 | },
51 | "ClientSideTargetResolution": {
52 | "adapter": {
53 | "config": {
54 | "inbounds": {
55 | "BrowseBooks": {
56 | "semanticObject": "Books",
57 | "action": "display",
58 | "title": "Browse Books",
59 | "signature": {
60 | "parameters": {
61 | "Books.ID": {
62 | "renameTo": "ID"
63 | },
64 | "Authors.books.ID": {
65 | "renameTo": "ID"
66 | }
67 | },
68 | "additionalParameters": "ignored"
69 | },
70 | "resolutionResult": {
71 | "applicationType": "SAPUI5",
72 | "additionalInformation": "SAPUI5.Component=bookshop",
73 | "url": "browse/webapp"
74 | }
75 | },
76 | "ManageBooks": {
77 | "semanticObject": "Books",
78 | "action": "manage",
79 | "title": "Manage Books",
80 | "signature": {
81 | "parameters": {},
82 | "additionalParameters": "allowed"
83 | },
84 | "resolutionResult": {
85 | "applicationType": "SAPUI5",
86 | "additionalInformation": "SAPUI5.Component=books",
87 | "url": "admin-books/webapp"
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/srv/server.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import cds from '@sap/cds'
3 | import * as path from 'path'
4 | import { existsSync as fileExists } from 'fs'
5 | import { fileURLToPath } from 'url'
6 | import upath from 'upath'
7 | import { glob } from 'glob'
8 | import express from 'express'
9 |
10 | // @ts-ignore
11 | const __dirname = fileURLToPath(new URL('.', import.meta.url))
12 | global.__base = __dirname
13 | let app
14 |
15 | cds
16 | .on('bootstrap', async function (_app) {
17 | app = _app
18 | let expressFile = path.join(__dirname, './server/express.js')
19 | if (fileExists(expressFile)) {
20 | const { default: expressFileExc } = await import(`file://${expressFile}`)
21 | expressFileExc(app, cds)
22 | }
23 | })
24 |
25 | .on('serving', service => {
26 | addLinkToGraphQl(service)
27 | addCustomLinks(service)
28 |
29 | app.use('/model/', async (req, res) => {
30 | const csn = await cds.load('db')
31 | const model = cds.reflect(csn)
32 | res.type('json')
33 | res.send(JSON.stringify(model))
34 | })
35 | })
36 |
37 | function addLinkToGraphQl(service) {
38 | const provider = (entity) => {
39 | if (entity) return // avoid link on entity level, looks too messy
40 | return { href: 'graphql', name: 'GraphQL', title: 'Show in GraphQL' }
41 | }
42 | // Needs @sap/cds >= 4.4.0
43 | service.$linkProviders ? service.$linkProviders.push(provider) : service.$linkProviders = [provider]
44 | }
45 |
46 | function addCustomLinks(service) {
47 | const providerStatus = (entity) => {
48 | if (entity) return // avoid link on entity level, looks too messy
49 | return { href: 'status', name: 'Express Status', title: 'Show realtime Express status' }
50 | }
51 | const providerHealth = (entity) => {
52 | if (entity) return // avoid link on entity level, looks too messy
53 | return { href: 'healthcheck', name: 'Health Check', title: 'Health Check Status' }
54 | }
55 | const providerSwagger = (entity) => {
56 | if (entity) return // avoid link on entity level, looks too messy
57 | return { href: 'apiJS/api-docs', name: 'Swagger UI', title: 'Test Node.js Endpoints with Swagger' }
58 | }
59 | service.$linkProviders ? service.$linkProviders.push(providerStatus) : service.$linkProviders = [providerStatus]
60 | service.$linkProviders ? service.$linkProviders.push(providerHealth) : service.$linkProviders = [providerHealth]
61 | service.$linkProviders ? service.$linkProviders.push(providerSwagger) : service.$linkProviders = [providerSwagger]
62 |
63 | }
64 |
65 | export default async function (o) {
66 | o.app = express()
67 | o.app.httpServer = await cds.server(o)
68 | //Load routes
69 | let routesDir = path.join(__dirname, './routes/**/*.js')
70 | let files = await glob(upath.normalize(routesDir))
71 | if (files.length !== 0) {
72 | let modules = await Promise.all(
73 | files.map((file) => {
74 | cds.log('nodejs').info(`Loading: ${file}`)
75 | return import(`file://${file}`)
76 | })
77 | )
78 | modules.forEach((module) => module.load(o.app, o.app.httpServer))
79 | }
80 | return o.app.httpServer
81 | }
82 |
--------------------------------------------------------------------------------
/app/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Bookshop
10 |
68 |
69 |
70 |
76 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/app/indexFLP.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Bookshop
10 |
68 |
69 |
70 |
77 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/srv/routes/await.js:
--------------------------------------------------------------------------------
1 | import toc from '../utils/exampleTOC.js'
2 | import util from 'util'
3 | import fs from 'fs'
4 |
5 | export function load(app) {
6 | const readFilePromisified = util.promisify(fs.readFile)
7 |
8 | //Hello Router
9 | app.get("/rest/await", (req, res) => {
10 | var output =
11 | `Async/Await - Better Promises
12 | /await - Await readFile
13 | /awaitError - await readFile and catch error
14 | /awaitDB1 - Simple HANA DB Select via Await`
15 | toc()
16 | return res.type("text/html").status(200).send(output)
17 | })
18 |
19 | /**
20 | * @swagger
21 | *
22 | * /rest/await/await:
23 | * get:
24 | * summary: Await readFile
25 | * tags:
26 | * - await
27 | * responses:
28 | * '200':
29 | * description: await info
30 | */
31 | app.get("/rest/await/await", async (req, res, next) => {
32 | try {
33 | const text = await readFilePromisified(global.__base + "async/file.txt")
34 | return res.type("text/html").status(200).send(text)
35 | } catch (e) {
36 | app.log.error(e)
37 | let error = new cds.error(e)
38 | return next(error)
39 | }
40 | })
41 |
42 | /**
43 | * @swagger
44 | *
45 | * /rest/await/awaitError:
46 | * get:
47 | * summary: Await readFile and catch error
48 | * tags:
49 | * - await
50 | * responses:
51 | * '200':
52 | * description: await info
53 | */
54 | app.get("/rest/await/awaitError", async (req, res, next) => {
55 | try {
56 | const text = await readFilePromisified(global.__base + "async/missing.txt")
57 | return res.type("text/html").status(200).send(text)
58 | } catch (e) {
59 | app.log.error(e)
60 | let error = new cds.error(e)
61 | return next(error)
62 | }
63 | })
64 |
65 | /**
66 | * @swagger
67 | *
68 | * /rest/await/awaitDB1:
69 | * get:
70 | * summary: Simple Database Select Await
71 | * tags:
72 | * - await
73 | * responses:
74 | * '200':
75 | * description: await info
76 | */
77 | app.get("/rest/await/awaitDB1", async (req, res, next) => {
78 | try {
79 | console.log(cds.env.requires.db.kind)
80 | let dbQuery = ""
81 | switch (cds.env.requires.db.kind) {
82 | case 'hana':
83 | dbQuery = SELECT
84 | .columns("SESSION_USER")
85 | .from("DUMMY")
86 | break
87 | case 'sqlite':
88 | dbQuery = SELECT
89 | .from("sqlite_master")
90 | break
91 | default:
92 | break
93 | }
94 | let results = await cds.run(dbQuery)
95 | let result = JSON.stringify({
96 | Objects: results
97 | })
98 | return res.type("application/json").status(200).send(result)
99 | } catch (e) {
100 | app.log.error(e)
101 | let error = new cds.error(e)
102 | return next(error)
103 | }
104 | })
105 |
106 | return app
107 | }
--------------------------------------------------------------------------------
/srv/async/database.js:
--------------------------------------------------------------------------------
1 |
2 | export function callDB(wss) {
3 | try {
4 | let dbQuery = SELECT
5 | .from(cds.entities.Books)
6 | .columns("title", "price")
7 | .limit(10)
8 | wss.broadcast("Database Connected")
9 | cds.run(dbQuery)
10 | .then(results => {
11 | wss.broadcast("Database Call Complete")
12 | for (const item of results) {
13 | wss.broadcast(`${item.title}: ${item.price}\n`)
14 | }
15 | })
16 | .catch(error => {
17 | cds.log('nodejs').error(error)
18 | })
19 | } catch (e) {
20 | cds.log('nodejs').error(e)
21 | }
22 | }
23 |
24 | export function callDB1(wss) {
25 | try {
26 | let dbQuery = SELECT
27 | .from(cds.entities.Books)
28 | .columns("title", "price")
29 | .limit(25)
30 | wss.broadcast("Database Connected #1")
31 | cds.run(dbQuery)
32 | .then(results => {
33 | wss.broadcast("Database Call #1")
34 | wss.broadcast("--Books")
35 | for (const item of results) {
36 | wss.broadcast(`${item.title}: ${item.price}\n`)
37 | }
38 | wss.broadcast("End Waterfall #1")
39 | })
40 | .catch(error => {
41 | cds.log('nodejs').error(error)
42 | })
43 | } catch (e) {
44 | cds.log('nodejs').error(e)
45 | }
46 | }
47 |
48 | export function callDB2(wss) {
49 | try {
50 | let dbQuery = SELECT
51 | .from(cds.entities.Authors)
52 | .columns("name", "dateOfBirth")
53 | .limit(2)
54 | wss.broadcast("Database Connected #2")
55 | cds.run(dbQuery)
56 | .then(results => {
57 | wss.broadcast("Database Call #2")
58 | wss.broadcast("--Authors")
59 | for (const item of results) {
60 | wss.broadcast(`${item.name}: ${item.dateOfBirth}\n`)
61 | }
62 | wss.broadcast("End Waterfall #2")
63 | })
64 | .catch(error => {
65 | cds.log('nodejs').error(error)
66 | })
67 | } catch (e) {
68 | cds.log('nodejs').error(e)
69 | }
70 | }
71 |
72 | export async function callDB3(wss) {
73 | try {
74 | let dbQuery = SELECT
75 | .from(cds.entities.Books)
76 | .columns("title", "price")
77 | .limit(25)
78 | wss.broadcast("Database Connected #1")
79 | const results = await cds.run(dbQuery)
80 | wss.broadcast("Database Call #1")
81 | wss.broadcast("--Books")
82 | for (const item of results) {
83 | wss.broadcast(`${item.title}: ${item.price}\n`)
84 | }
85 | wss.broadcast("End Waterfall #1")
86 | } catch (e) {
87 | cds.log('nodejs').error(e)
88 | }
89 | }
90 |
91 | export async function callDB4(wss) {
92 | try {
93 | let dbQuery = SELECT
94 | .from(cds.entities.Authors)
95 | .columns("name", "dateOfBirth")
96 | .limit(2)
97 | wss.broadcast("Database Connected #2")
98 | const results = await cds.run(dbQuery)
99 | wss.broadcast("Database Call #2")
100 | wss.broadcast("--Authors")
101 | for (const item of results) {
102 | wss.broadcast(`${item.name}: ${item.dateOfBirth}\n`)
103 | }
104 | wss.broadcast("End Waterfall #2")
105 | } catch (e) {
106 | cds.log('nodejs').error(e)
107 | }
108 | }
--------------------------------------------------------------------------------
/app/browse/webapp/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.49.0",
3 | "sap.app": {
4 | "id": "bookshop",
5 | "applicationVersion": {
6 | "version": "1.0.0"
7 | },
8 | "type": "application",
9 | "title": "{{appTitle}}",
10 | "description": "{{appDescription}}",
11 | "i18n": "i18n/i18n.properties",
12 | "dataSources": {
13 | "CatalogService": {
14 | "uri": "/odata/v4/catalog/",
15 | "type": "OData",
16 | "settings": {
17 | "odataVersion": "4.0"
18 | }
19 | }
20 | },
21 | "crossNavigation": {
22 | "inbounds": {
23 | "intent1": {
24 | "signature": {
25 | "parameters": {
26 | "Books.ID": {
27 | "renameTo": "ID"
28 | },
29 | "Authors.books.ID": {
30 | "renameTo": "ID"
31 | }
32 | },
33 | "additionalParameters": "ignored"
34 | },
35 | "semanticObject": "Books",
36 | "action": "display",
37 | "title": "{{appTitle}}",
38 | "info": "{{appInfo}}",
39 | "subTitle": "{{appSubTitle}}",
40 | "icon": "sap-icon://course-book",
41 | "indicatorDataSource": {
42 | "dataSource": "CatalogService",
43 | "path": "Books/$count",
44 | "refresh": 1800
45 | }
46 | }
47 | }
48 | }
49 | },
50 | "sap.ui": {
51 | "technology": "UI5",
52 | "fullWidth": false,
53 | "deviceTypes": {
54 | "desktop": true,
55 | "tablet": true,
56 | "phone": true
57 | }
58 | },
59 | "sap.ui5": {
60 | "dependencies": {
61 | "minUI5Version": "1.115.1",
62 | "libs": {
63 | "sap.fe.templates": {}
64 | }
65 | },
66 | "models": {
67 | "i18n": {
68 | "type": "sap.ui.model.resource.ResourceModel",
69 | "uri": "i18n/i18n.properties"
70 | },
71 | "": {
72 | "dataSource": "CatalogService",
73 | "settings": {
74 | "synchronizationMode": "None",
75 | "operationMode": "Server",
76 | "autoExpandSelect": true,
77 | "earlyRequests": true,
78 | "groupProperties": {
79 | "default": {
80 | "submit": "Auto"
81 | }
82 | }
83 | }
84 | }
85 | },
86 | "routing": {
87 | "routes": [
88 | {
89 | "pattern": ":?query:",
90 | "name": "BooksList",
91 | "target": "BooksList"
92 | },
93 | {
94 | "pattern": "Books({key}):?query:",
95 | "name": "BooksDetails",
96 | "target": "BooksDetails"
97 | }
98 | ],
99 | "targets": {
100 | "BooksList": {
101 | "type": "Component",
102 | "id": "BooksList",
103 | "name": "sap.fe.templates.ListReport",
104 | "options": {
105 | "settings": {
106 | "entitySet": "Books",
107 | "initialLoad": true,
108 | "navigation": {
109 | "Books": {
110 | "detail": {
111 | "route": "BooksDetails"
112 | }
113 | }
114 | }
115 | }
116 | }
117 | },
118 | "BooksDetails": {
119 | "type": "Component",
120 | "id": "BooksDetailsList",
121 | "name": "sap.fe.templates.ObjectPage",
122 | "options": {
123 | "settings": {
124 | "entitySet": "Books"
125 | }
126 | }
127 | }
128 | }
129 | },
130 | "contentDensities": {
131 | "compact": true,
132 | "cozy": true
133 | }
134 | },
135 | "sap.fiori": {
136 | "registrationIds": [],
137 | "archeType": "transactional"
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/resources/logout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CAP JavaScript Basics Learning
5 |
6 |
38 |
39 |
40 |
41 |
47 |
48 |
59 |
60 |
61 |
62 |
Goodbye
63 |
You have logged out
64 |
67 |
68 |
74 |
75 |
--------------------------------------------------------------------------------
/app/admin-books/webapp/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "_version": "1.49.0",
3 | "sap.app": {
4 | "applicationVersion": {
5 | "version": "1.0.0"
6 | },
7 | "id": "books",
8 | "type": "application",
9 | "title": "{{appTitle}}",
10 | "description": "{{appDescription}}",
11 | "i18n": "i18n/i18n.properties",
12 | "dataSources": {
13 | "AdminService": {
14 | "uri": "/odata/v4/admin/",
15 | "type": "OData",
16 | "settings": {
17 | "odataVersion": "4.0"
18 | }
19 | }
20 | }
21 | },
22 | "sap.ui": {
23 | "technology": "UI5",
24 | "fullWidth": false,
25 | "deviceTypes": {
26 | "desktop": true,
27 | "tablet": true,
28 | "phone": true
29 | }
30 | },
31 | "sap.ui5": {
32 | "dependencies": {
33 | "minUI5Version": "1.115.1",
34 | "libs": {
35 | "sap.fe.templates": {}
36 | }
37 | },
38 | "models": {
39 | "i18n": {
40 | "type": "sap.ui.model.resource.ResourceModel",
41 | "uri": "i18n/i18n.properties"
42 | },
43 | "": {
44 | "dataSource": "AdminService",
45 | "settings": {
46 | "synchronizationMode": "None",
47 | "operationMode": "Server",
48 | "autoExpandSelect": true,
49 | "earlyRequests": true,
50 | "groupProperties": {
51 | "default": {
52 | "submit": "Auto"
53 | }
54 | }
55 | }
56 | }
57 | },
58 | "routing": {
59 | "routes": [
60 | {
61 | "pattern": ":?query:",
62 | "name": "BooksList",
63 | "target": "BooksList"
64 | },
65 | {
66 | "pattern": "Books({key}):?query:",
67 | "name": "BooksDetails",
68 | "target": "BooksDetails"
69 | },
70 | {
71 | "pattern": "Books({key}/author({key2}):?query:",
72 | "name": "AuthorsDetails",
73 | "target": "AuthorsDetails"
74 | }
75 | ],
76 | "targets": {
77 | "BooksList": {
78 | "type": "Component",
79 | "id": "BooksList",
80 | "name": "sap.fe.templates.ListReport",
81 | "options": {
82 | "settings": {
83 | "entitySet": "Books",
84 | "initialLoad": true,
85 | "navigation": {
86 | "Books": {
87 | "detail": {
88 | "route": "BooksDetails"
89 | }
90 | }
91 | }
92 | }
93 | }
94 | },
95 | "BooksDetails": {
96 | "type": "Component",
97 | "id": "BooksDetailsList",
98 | "name": "sap.fe.templates.ObjectPage",
99 | "options": {
100 | "settings": {
101 | "entitySet": "Books",
102 | "navigation": {
103 | "Authors": {
104 | "detail": {
105 | "route": "AuthorsDetails"
106 | }
107 | }
108 | }
109 | }
110 | }
111 | },
112 | "AuthorsDetails": {
113 | "type": "Component",
114 | "id": "AuthorsDetailsList",
115 | "name": "sap.fe.templates.ObjectPage",
116 | "options": {
117 | "settings": {
118 | "entitySet": "Authors"
119 | }
120 | }
121 | }
122 | }
123 | },
124 | "contentDensities": {
125 | "compact": true,
126 | "cozy": true
127 | }
128 | },
129 | "sap.fiori": {
130 | "registrationIds": [],
131 | "archeType": "transactional"
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/xs-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcomeFile": "index.html",
3 | "authenticationMethod": "route",
4 | "logout": {
5 | "logoutPage": "/logout.html",
6 | "logoutEndpoint": "/my/logout"
7 | },
8 | "websockets": {
9 | "enabled": true
10 | },
11 | "routes": [
12 | {
13 | "source": "/status(.*)",
14 | "destination": "srv_api",
15 | "csrfProtection": true,
16 | "authenticationType": "xsuaa"
17 | },
18 | {
19 | "source": "/graphql/(.*)",
20 | "destination": "srv_api",
21 | "csrfProtection": false,
22 | "authenticationType": "none"
23 | },
24 | {
25 | "source": "/socket.io/(.*)",
26 | "destination": "srv_api",
27 | "csrfProtection": true,
28 | "authenticationType": "xsuaa"
29 | },
30 | {
31 | "source": "/apiJS/(.*)",
32 | "destination": "srv_api",
33 | "csrfProtection": false,
34 | "authenticationType": "xsuaa",
35 | "scope": {
36 | "GET": "$XSAPPNAME.Display",
37 | "POST": [
38 | "$XSAPPNAME.Create"
39 | ],
40 | "PUT": [
41 | "$XSAPPNAME.Create"
42 | ],
43 | "DELETE": [
44 | "$XSAPPNAME.Delete"
45 | ],
46 | "default": "$XSAPPNAME.Display"
47 | }
48 | },
49 | {
50 | "source": "/api/(.*)",
51 | "destination": "srv_api",
52 | "csrfProtection": false,
53 | "authenticationType": "xsuaa",
54 | "scope": {
55 | "GET": "$XSAPPNAME.Display",
56 | "POST": [
57 | "$XSAPPNAME.Create"
58 | ],
59 | "PUT": [
60 | "$XSAPPNAME.Create"
61 | ],
62 | "DELETE": [
63 | "$XSAPPNAME.Delete"
64 | ],
65 | "default": "$XSAPPNAME.Display"
66 | }
67 | },
68 | {
69 | "source": "/admin(.*)",
70 | "destination": "srv_api",
71 | "csrfProtection": false,
72 | "authenticationType": "xsuaa",
73 | "scope": {
74 | "GET": "$XSAPPNAME.Display",
75 | "POST": [
76 | "$XSAPPNAME.Create"
77 | ],
78 | "PUT": [
79 | "$XSAPPNAME.Create"
80 | ],
81 | "DELETE": [
82 | "$XSAPPNAME.Delete"
83 | ],
84 | "default": "$XSAPPNAME.Display"
85 | }
86 | },
87 | {
88 | "source": "/odata/(.*)",
89 | "destination": "srv_api",
90 | "csrfProtection": false,
91 | "authenticationType": "xsuaa",
92 | "scope": {
93 | "GET": "$XSAPPNAME.Display",
94 | "POST": [
95 | "$XSAPPNAME.Create"
96 | ],
97 | "PUT": [
98 | "$XSAPPNAME.Create"
99 | ],
100 | "DELETE": [
101 | "$XSAPPNAME.Delete"
102 | ],
103 | "default": "$XSAPPNAME.Display"
104 | }
105 | },
106 | {
107 | "source": "/rest(.*)",
108 | "destination": "srv_api",
109 | "csrfProtection": false,
110 | "authenticationType": "xsuaa",
111 | "scope": {
112 | "GET": "$XSAPPNAME.Display",
113 | "POST": [
114 | "$XSAPPNAME.Create"
115 | ],
116 | "PUT": [
117 | "$XSAPPNAME.Create"
118 | ],
119 | "DELETE": [
120 | "$XSAPPNAME.Delete"
121 | ],
122 | "default": "$XSAPPNAME.Display"
123 | }
124 | },
125 | {
126 | "source": "/sap/bc/lrep(.*)",
127 | "destination": "srv_api",
128 | "csrfProtection": true,
129 | "authenticationType": "xsuaa",
130 | "scope": {
131 | "GET": "$XSAPPNAME.Display",
132 | "POST": [
133 | "$XSAPPNAME.Display"
134 | ],
135 | "PUT": [
136 | "$XSAPPNAME.Display"
137 | ],
138 | "DELETE": [
139 | "$XSAPPNAME.Display"
140 | ],
141 | "default": "$XSAPPNAME.Display"
142 | }
143 | },
144 | {
145 | "source": "^/logout.html$",
146 | "localDir": "resources",
147 | "authenticationType": "none"
148 | },
149 | {
150 | "source": "/(.*)",
151 | "localDir": "resources",
152 | "authenticationType": "xsuaa",
153 | "scope": {
154 | "GET": "$XSAPPNAME.Display",
155 | "default": "$XSAPPNAME.Display"
156 | }
157 | }
158 | ]
159 | }
160 |
--------------------------------------------------------------------------------
/app/resources/exerciseAsync/controller/App.controller.js:
--------------------------------------------------------------------------------
1 | /*eslint no-console: 0, no-unused-vars: 0, no-use-before-define: 0, no-redeclare: 0, no-undef: 0, no-sequences: 0, no-unused-expressions: 0*/
2 | /*eslint-env es6 */
3 | //To use a javascript controller its name must end with .controller.js
4 | sap.ui.define([
5 | "sap/sample/common/controller/BaseController",
6 | "sap/ui/model/json/JSONModel",
7 | "sap/ui/core/ws/WebSocket",
8 | "sap/m/MessageToast"
9 | ], function (BaseController, JSONModel, WebSocket, MessageToast) {
10 | "use strict";
11 | var connection = new WebSocket("/rest/excAsync")
12 |
13 | return BaseController.extend("sap.sample.exerciseAsync.controller.App", {
14 |
15 | onInit: function () {
16 | // connection opened
17 | connection.attachOpen((oControlEvent) => {
18 | MessageToast.show("connection opened")
19 | })
20 |
21 | // server messages
22 | connection.attachMessage((oControlEvent) => {
23 | var oModel = this.getModel("chatModel")
24 | var eventData = oControlEvent.getParameter("data")
25 | var result = oModel.getData()
26 |
27 | var data = jQuery.parseJSON(eventData)
28 | var msg = data.text,
29 | lastInfo = result.chat
30 |
31 | if (lastInfo.length > 0) {
32 | lastInfo += "\r\n"
33 | }
34 | oModel.setData({
35 | chat: lastInfo + msg
36 | }, true)
37 | }, this)
38 |
39 | // error handling
40 | connection.attachError((oControlEvent) => {
41 | MessageToast.show("Websocket connection error")
42 | })
43 |
44 | // onConnectionClose
45 | connection.attachClose((oControlEvent) => {
46 | MessageToast.show("Websocket connection closed")
47 | })
48 | },
49 |
50 | // send message
51 | sendBasic: function () {
52 | let oModel = this.getOwnerComponent().getModel("chatModel")
53 | oModel.setData({
54 | chat: ""
55 | }, true)
56 | connection.send(JSON.stringify({
57 | action: "async"
58 | }))
59 | },
60 |
61 | sendFileS: function () {
62 | var oModel = this.getOwnerComponent().getModel("chatModel")
63 | oModel.setData({
64 | chat: ""
65 | }, true)
66 | connection.send(JSON.stringify({
67 | action: "fileSync"
68 | }))
69 | },
70 |
71 | sendFileA: function () {
72 | var oModel = this.getOwnerComponent().getModel("chatModel")
73 | oModel.setData({
74 | chat: ""
75 | }, true)
76 | connection.send(JSON.stringify({
77 | action: "fileAsync"
78 | }))
79 | },
80 |
81 | sendHTTP: function () {
82 | var oModel = this.getOwnerComponent().getModel("chatModel")
83 | oModel.setData({
84 | chat: ""
85 | }, true)
86 | connection.send(JSON.stringify({
87 | action: "httpClient"
88 | }))
89 | },
90 |
91 | sendHTTP2: function () {
92 | var oModel = this.getOwnerComponent().getModel("chatModel")
93 | oModel.setData({
94 | chat: ""
95 | }, true)
96 | connection.send(JSON.stringify({
97 | action: "httpClient2"
98 | }))
99 | },
100 |
101 | sendDB1: function () {
102 | var oModel = this.getOwnerComponent().getModel("chatModel")
103 | oModel.setData({
104 | chat: ""
105 | }, true)
106 | connection.send(JSON.stringify({
107 | action: "dbAsync"
108 | }))
109 | },
110 |
111 | sendDB2: function () {
112 | var oModel = this.getOwnerComponent().getModel("chatModel")
113 | oModel.setData({
114 | chat: ""
115 | }, true)
116 | connection.send(JSON.stringify({
117 | action: "dbAsync2"
118 | }))
119 | }
120 |
121 | })
122 | })
--------------------------------------------------------------------------------
/srv/routes/exerciseAsync.js:
--------------------------------------------------------------------------------
1 | import { WebSocketServer } from 'ws'
2 | import toc from '../utils/exampleTOC.js'
3 | import asyncLib from '../async/async.js'
4 | import dbAsync from '../async/databaseAsync.js'
5 | import dbAsync2 from '../async/databaseAsync2.js'
6 | import fileSync from '../async/fileSync.js'
7 | import fileAsync from '../async/fileAsync.js'
8 | import httpClient from '../async/httpClient.js'
9 | import httpClient2 from '../async/httpClient2.js'
10 |
11 | export function load(app, server) {
12 | app.use('/rest/excAsync', (req, res) => {
13 | let output =
14 | `Asynchronous Examples
15 | /exerciseAsync - Test Framework for Async Examples` +
16 | toc()
17 | res.type("text/html").status(200).send(output)
18 | })
19 |
20 | try {
21 | //Create Server
22 | var wss = new WebSocketServer({
23 | noServer: true,
24 | path: "/rest/excAsync"
25 | })
26 |
27 | //Handle Upgrade Event
28 | server.on("upgrade", (request, socket, head) => {
29 | console.log(request.url)
30 | if (request.url === '/rest/excAsync') {
31 | wss.handleUpgrade(request, socket, head, (ws) => {
32 | wss.emit("connection", ws, request)
33 | })
34 | }
35 | })
36 |
37 | //Broadcast function
38 | wss.broadcast = (data, progress) => {
39 | let message = JSON.stringify({
40 | text: data,
41 | progress: progress
42 | })
43 | wss.clients.forEach((client) => {
44 | try {
45 | client.send(message, (error) => {
46 | if (error !== null && typeof error !== "undefined") {
47 | cds.log('nodejs').error(`Send Error: ${error}`)
48 | }
49 | })
50 | } catch (e) {
51 | cds.log('nodejs').error(`Broadcast Error: ${e}`)
52 | }
53 | })
54 | cds.log('nodejs').info(`Sent: ${message}`)
55 | }
56 |
57 | //Web Socket Error Handler
58 | wss.on("error", (error) => {
59 | cds.log('nodejs').error(`Web Socket Server Error: ${error}`)
60 | })
61 |
62 | //Web Socket Connection
63 | wss.on("connection", (ws) => {
64 | cds.log('nodejs').info("Connected")
65 |
66 | //Web Socket Message Received
67 | ws.on("message", (message) => {
68 | cds.log('nodejs').info(`Received: ${message}`)
69 | let data = JSON.parse(message)
70 | switch (data.action) {
71 | case "async":
72 | asyncLib(wss)
73 | break
74 | case "fileSync":
75 | fileSync(wss)
76 | break
77 | case "fileAsync":
78 | fileAsync(wss)
79 | break
80 | case "httpClient":
81 | httpClient(wss)
82 | break
83 | case "httpClient2":
84 | httpClient2(wss)
85 | break
86 | case "dbAsync":
87 | dbAsync(wss)
88 | break
89 | case "dbAsync2":
90 | dbAsync2(wss)
91 | break
92 | default:
93 | wss.broadcast(`Error: Undefined Action: ${data.action}`)
94 | break
95 | }
96 | })
97 |
98 | //Web Socket Connection Close
99 | ws.on("close", () => {
100 | cds.log('nodejs').info("Closed")
101 | })
102 |
103 | //Web Socket Connection Error
104 | ws.on("error", (error) => {
105 | cds.log('nodejs').error(`Web Socket Error: ${error}`)
106 | })
107 |
108 | ws.send(JSON.stringify({
109 | text: "Connected to Exercise 3"
110 | }), (error) => {
111 | if (error !== null && typeof error !== "undefined") {
112 | cds.log('nodejs').error(`Send Error: ${error}`)
113 | }
114 | })
115 | })
116 |
117 | } catch (e) {
118 | cds.log('nodejs').error(e)
119 | }
120 | return app
121 | }
--------------------------------------------------------------------------------
/app/resources/common/controller/BaseController.js:
--------------------------------------------------------------------------------
1 | /*global history */
2 | /* eslint-disable no-undef */
3 | sap.ui.define([
4 | "sap/ui/core/mvc/Controller",
5 | "sap/ui/core/routing/History",
6 | "sap/ui/core/Fragment",
7 | "sap/ui/core/syncStyleClass",
8 | ], function (Controller, History, Fragment, syncStyleClass) {
9 | "use strict";
10 |
11 | return Controller.extend("sap.sample.common.controller.BaseController", {
12 | /**
13 | * Convenience method for accessing the router in every controller of the application.
14 | * @public
15 | * @returns {sap.ui.core.routing.Router} the router for this component
16 | */
17 | getRouter: function () {
18 | return this.getOwnerComponent().getRouter();
19 | },
20 |
21 | /**
22 | * Convenience method for getting the view model by name in every controller of the application.
23 | * @public
24 | * @param {string} sName the model name
25 | * @returns {sap.ui.model.Model} the model instance
26 | */
27 | getModel: function (sName) {
28 | return this.getView().getModel(sName);
29 | },
30 |
31 | /**
32 | * Convenience method for setting the view model in every controller of the application.
33 | * @public
34 | * @param {sap.ui.model.Model} oModel the model instance
35 | * @param {string} sName the model name
36 | * @returns {sap.ui.mvc.View} the view instance
37 | */
38 | setModel: function (oModel, sName) {
39 | return this.getView().setModel(oModel, sName);
40 | },
41 |
42 | /**
43 | * Convenience method for getting the resource bundle.
44 | * @public
45 | * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component
46 | */
47 | getResourceBundle: function () {
48 | return this.getOwnerComponent().getModel("i18n").getResourceBundle();
49 | },
50 |
51 | /**
52 | * Event handler for navigating back.
53 | * It there is a history entry we go one step back in the browser history
54 | * If not, it will replace the current entry of the browser history with the master route.
55 | * @public
56 | */
57 | onNavBack: function () {
58 | var sPreviousHash = History.getInstance().getPreviousHash();
59 |
60 | if (sPreviousHash !== undefined) {
61 | history.go(-1);
62 | } else {
63 | this.getRouter().navTo("master", {}, true)
64 | }
65 | },
66 |
67 | openUrl: function (url, newTab) {
68 | // Require the URLHelper and open the URL in a new window or tab (same as _blank):
69 | sap.ui.require(["sap/m/library"], ({ URLHelper }) => URLHelper.redirect(url, newTab));
70 | },
71 |
72 | startBusy: function () {
73 | if (!this._pBusyDialog) {
74 | this._pBusyDialog = Fragment.load({
75 | name: "sap.common.view.BusyDialog",
76 | controller: this
77 | }).then(function (oBusyDialog) {
78 | this.getView().addDependent(oBusyDialog)
79 | syncStyleClass("sapUiSizeCompact", this.getView(), oBusyDialog)
80 | return oBusyDialog
81 | }.bind(this))
82 | }
83 |
84 | this._pBusyDialog.then(function (oBusyDialog) {
85 | oBusyDialog.open()
86 | }.bind(this))
87 | },
88 | endBusy: function (oController) {
89 | if (oController._pBusyDialog) {
90 | oController._pBusyDialog.then(function (oBusyDialog) {
91 | oBusyDialog.close()
92 | })
93 | }
94 | },
95 |
96 | onErrorCall: function (oError, oController) {
97 | if (oController) {
98 | oController.endBusy(oController)
99 | }
100 | sap.ui.require(["sap/m/MessageBox"], (MessageBox) => {
101 | console.log(oError)
102 | if (oError.statusCode === 500 || oError.statusCode === 400 || oError.statusCode === "500" || oError.statusCode === "400" || oError.status === 500) {
103 | var errorRes = oError.responseText
104 | MessageBox.alert(errorRes)
105 | return
106 | } else {
107 | MessageBox.alert(oError.statusText)
108 | return
109 | }
110 | })
111 | }
112 |
113 | })
114 |
115 | }
116 | )
--------------------------------------------------------------------------------
/app/common.cds:
--------------------------------------------------------------------------------
1 | /*
2 | Common Annotations shared by all apps
3 | */
4 |
5 | using { sap.capire.bookshop as my } from '../db/schema';
6 | using { sap.common, sap.common.Currencies } from '@sap/cds/common';
7 |
8 | ////////////////////////////////////////////////////////////////////////////
9 | //
10 | // Books Lists
11 | //
12 | annotate my.Books with @(
13 | Common.SemanticKey: [ID],
14 | UI: {
15 | Identification: [{ Value: title }],
16 | SelectionFields: [
17 | ID,
18 | author_ID,
19 | price,
20 | currency_code
21 | ],
22 | LineItem: [
23 | { Value: ID, Label: '{i18n>Title}' },
24 | { Value: author.ID, Label: '{i18n>Author}' },
25 | { Value: genre.name },
26 | { Value: stock },
27 | { Value: price },
28 | { Value: currency.symbol },
29 | ]
30 | }
31 | ) {
32 | ID @Common: {
33 | SemanticObject: 'Books',
34 | Text: title,
35 | TextArrangement: #TextOnly
36 | };
37 | author @ValueList.entity: 'Authors';
38 | };
39 |
40 | annotate Currencies with {
41 | symbol @Common.Label: '{i18n>Currency}';
42 | }
43 |
44 | ////////////////////////////////////////////////////////////////////////////
45 | //
46 | // Books Details
47 | //
48 | annotate my.Books with @(UI : {HeaderInfo : {
49 | TypeName : '{i18n>Book}',
50 | TypeNamePlural: '{i18n>Books}',
51 | Title : { Value: title },
52 | Description : { Value: author.name }
53 | }, });
54 |
55 |
56 | ////////////////////////////////////////////////////////////////////////////
57 | //
58 | // Books Elements
59 | //
60 | annotate my.Books with {
61 | ID @title: '{i18n>ID}';
62 | title @title: '{i18n>Title}';
63 | genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
64 | author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
65 | price @title: '{i18n>Price}' @Measures.ISOCurrency: currency_code;
66 | stock @title: '{i18n>Stock}';
67 | descr @title: '{i18n>Description}' @UI.MultiLineText;
68 | image @title: '{i18n>Image}';
69 | }
70 |
71 | ////////////////////////////////////////////////////////////////////////////
72 | //
73 | // Genres List
74 | //
75 | annotate my.Genres with @(
76 | Common.SemanticKey: [name],
77 | UI: {
78 | SelectionFields: [name],
79 | LineItem: [
80 | { Value: name },
81 | {
82 | Value: parent.name,
83 | Label: 'Main Genre'
84 | },
85 | ],
86 | }
87 | );
88 |
89 | annotate my.Genres with {
90 | ID @Common.Text : name @Common.TextArrangement : #TextOnly;
91 | }
92 |
93 | ////////////////////////////////////////////////////////////////////////////
94 | //
95 | // Genre Details
96 | //
97 | annotate my.Genres with @(UI : {
98 | Identification: [{ Value: name}],
99 | HeaderInfo: {
100 | TypeName : '{i18n>Genre}',
101 | TypeNamePlural: '{i18n>Genres}',
102 | Title : { Value: name },
103 | Description : { Value: ID }
104 | },
105 | Facets: [{
106 | $Type : 'UI.ReferenceFacet',
107 | Label : '{i18n>SubGenres}',
108 | Target: 'children/@UI.LineItem'
109 | }, ],
110 | });
111 |
112 | ////////////////////////////////////////////////////////////////////////////
113 | //
114 | // Genres Elements
115 | //
116 | annotate my.Genres with {
117 | ID @title: '{i18n>ID}';
118 | name @title: '{i18n>Genre}';
119 | }
120 |
121 | ////////////////////////////////////////////////////////////////////////////
122 | //
123 | // Authors List
124 | //
125 | annotate my.Authors with @(
126 | Common.SemanticKey: [ID],
127 | UI: {
128 | Identification : [{ Value: name}],
129 | SelectionFields: [ name ],
130 | LineItem : [
131 | { Value: ID },
132 | { Value: dateOfBirth },
133 | { Value: dateOfDeath },
134 | { Value: placeOfBirth },
135 | { Value: placeOfDeath },
136 | ],
137 | }
138 | ) {
139 | ID @Common: {
140 | SemanticObject: 'Authors',
141 | Text: name,
142 | TextArrangement: #TextOnly,
143 | };
144 | };
145 |
146 | ////////////////////////////////////////////////////////////////////////////
147 | //
148 | // Author Details
149 | //
150 | annotate my.Authors with @(UI : {
151 | HeaderInfo: {
152 | TypeName : '{i18n>Author}',
153 | TypeNamePlural: '{i18n>Authors}',
154 | Title : { Value: name },
155 | Description : { Value: dateOfBirth }
156 | },
157 | Facets: [{
158 | $Type : 'UI.ReferenceFacet',
159 | Target: 'books/@UI.LineItem'
160 | }],
161 | });
162 |
163 |
164 | ////////////////////////////////////////////////////////////////////////////
165 | //
166 | // Authors Elements
167 | //
168 | annotate my.Authors with {
169 | ID @title: '{i18n>ID}';
170 | name @title: '{i18n>Name}';
171 | dateOfBirth @title: '{i18n>DateOfBirth}';
172 | dateOfDeath @title: '{i18n>DateOfDeath}';
173 | placeOfBirth @title: '{i18n>PlaceOfBirth}';
174 | placeOfDeath @title: '{i18n>PlaceOfDeath}';
175 | }
176 |
177 | ////////////////////////////////////////////////////////////////////////////
178 | //
179 | // Languages List
180 | //
181 | annotate common.Languages with @(
182 | Common.SemanticKey: [code],
183 | Identification: [{ Value: code }],
184 | UI: {
185 | SelectionFields: [ name, descr ],
186 | LineItem: [
187 | { Value: code },
188 | { Value: name },
189 | ],
190 | }
191 | );
192 |
193 | ////////////////////////////////////////////////////////////////////////////
194 | //
195 | // Language Details
196 | //
197 | annotate common.Languages with @(UI : {
198 | HeaderInfo: {
199 | TypeName : '{i18n>Language}',
200 | TypeNamePlural: '{i18n>Languages}',
201 | Title : { Value: name },
202 | Description : { Value: descr }
203 | },
204 | Facets: [{
205 | $Type : 'UI.ReferenceFacet',
206 | Label : '{i18n>Details}',
207 | Target: '@UI.FieldGroup#Details'
208 | }, ],
209 | FieldGroup #Details: {Data : [
210 | { Value: code },
211 | { Value: name },
212 | { Value: descr }
213 | ]},
214 | });
215 |
216 | ////////////////////////////////////////////////////////////////////////////
217 | //
218 | // Currencies List
219 | //
220 | annotate common.Currencies with @(
221 | Common.SemanticKey: [code],
222 | Identification: [{ Value: code}],
223 | UI: {
224 | SelectionFields: [
225 | name,
226 | descr
227 | ],
228 | LineItem: [
229 | { Value: descr },
230 | { Value: symbol },
231 | { Value: code },
232 | ],
233 | }
234 | );
235 |
236 | ////////////////////////////////////////////////////////////////////////////
237 | //
238 | // Currency Details
239 | //
240 | annotate common.Currencies with @(UI : {
241 | HeaderInfo: {
242 | TypeName : '{i18n>Currency}',
243 | TypeNamePlural: '{i18n>Currencies}',
244 | Title : { Value: descr },
245 | Description : { Value: code }
246 | },
247 | Facets: [
248 | {
249 | $Type : 'UI.ReferenceFacet',
250 | Label : '{i18n>Details}',
251 | Target: '@UI.FieldGroup#Details'
252 | }
253 | ],
254 | FieldGroup #Details: {Data : [
255 | { Value: name },
256 | { Value: symbol },
257 | { Value: code },
258 | { Value: descr }
259 | ]}
260 | });
261 |
--------------------------------------------------------------------------------
/srv/routes/oo.js:
--------------------------------------------------------------------------------
1 | import { getLocaleReq } from '../utils/locale.js'
2 | import ooTutorial1 from '../oo/ooTutorial1.js'
3 | import ooTutorial2 from '../oo/ooTutorial2.js'
4 | import ooTutorial3 from '../oo/ooTutorial3.js'
5 | import ooTutorial4 from '../oo/ooTutorial4.js'
6 |
7 | export function load (app) {
8 |
9 | //Hello Router
10 | app.get("/rest/oo/", (req, res) => {
11 | let output =
12 | `JavaScript Object Oriented
13 | /classses1 - Classes
14 | /classses1Error - Classes, catch errors
15 | /classes2a - Classes with Static Methods #1
16 | /classes2b - Classes with Static Methods #2
17 | /classes3a - Classes with Instance Methods #1
18 | /classes3b - Classes with Instance Methods #2
19 | /classes4a - Classes with Inherited Methods #1
20 | /classes4b - Classes with Inherited Methods #2`
21 | res.type("text/html").status(200).send(output)
22 | })
23 |
24 | /**
25 | * @swagger
26 | *
27 | * /rest/oo/classes1:
28 | * get:
29 | * summary: OO - First Method Call
30 | * tags:
31 | * - oo
32 | * responses:
33 | * '200':
34 | * description: Output
35 | */
36 | app.get("/rest/oo/classes1", (req, res) => {
37 | let class1 = new ooTutorial1("first example")
38 | return res.type("text/html").status(200).send(`Call First Method: ${class1.myFirstMethod(5)}`)
39 | })
40 |
41 | /**
42 | * @swagger
43 | *
44 | * /rest/oo/classes1Error:
45 | * get:
46 | * summary: OO - Call and catch errors
47 | * tags:
48 | * - oo
49 | * responses:
50 | * '200':
51 | * description: Output
52 | */
53 | app.get("/rest/oo/classes1Error", (req, res, next) => {
54 | let class1 = new ooTutorial1("first example")
55 | try {
56 | return class1.myFirstMethod(20)
57 | } catch (e) {
58 | app.log.error(e)
59 | let error = new cds.error(e)
60 | return next(error)
61 | }
62 | })
63 |
64 | /**
65 | * @swagger
66 | *
67 | * /rest/oo/classes2a:
68 | * get:
69 | * summary: OO - Call static method
70 | * tags:
71 | * - oo
72 | * responses:
73 | * '200':
74 | * description: Output
75 | */
76 | app.get("/rest/oo/classes2a", async (req, res, next) => {
77 | try {
78 | const results = await ooTutorial2.getBookDetails("Wuthering Heights")
79 | return res.type("text/html").status(200).send(
80 | `Call Static Method: ${JSON.stringify(results)}`)
81 | } catch (e) {
82 | app.log.error(e)
83 | let error = new cds.error(e)
84 | return next(error)
85 | }
86 | })
87 |
88 | /**
89 | * @swagger
90 | *
91 | * /rest/oo/classes2b:
92 | * get:
93 | * summary: OO - Call Static Method #2
94 | * tags:
95 | * - oo
96 | * responses:
97 | * '200':
98 | * description: Output
99 | */
100 | app.get("/rest/oo/classes2b", async (req, res, next) => {
101 | try {
102 | const results = await ooTutorial2.calculateBookPrice("Wuthering Heights")
103 | let cur = new Intl.NumberFormat(getLocaleReq(req), { style: "currency", currency: results.currency })
104 | return res.type("text/html").status(200).send(
105 | `Call Static Method - Calc Price: ${cur.format(results.price)}`)
106 | } catch (e) {
107 | app.log.error(e)
108 | let error = new cds.error(e)
109 | return next(error)
110 | }
111 | })
112 |
113 | /**
114 | * @swagger
115 | *
116 | * /rest/oo/classes3a:
117 | * get:
118 | * summary: OO - Call Instance Method
119 | * tags:
120 | * - oo
121 | * responses:
122 | * '200':
123 | * description: Output
124 | */
125 | app.get("/rest/oo/classes3a", async (req, res, next) => {
126 | try {
127 | let class3 = new ooTutorial3("Wuthering Heights")
128 | const results = await class3.getBookDetails()
129 | return res.type("text/html").status(200).send(
130 | `Call Instance Method: ${JSON.stringify(results)}`)
131 | } catch (e) {
132 | app.log.error(e)
133 | let error = new cds.error(e)
134 | return next(error)
135 | }
136 | })
137 |
138 | /**
139 | * @swagger
140 | *
141 | * /rest/oo/classes3b:
142 | * get:
143 | * summary: OO - Call Instance Method #2
144 | * tags:
145 | * - oo
146 | * responses:
147 | * '200':
148 | * description: Output
149 | */
150 | app.get("/rest/oo/classes3b", async (req, res, next) => {
151 | try {
152 | let class3 = new ooTutorial3("Wuthering Heights")
153 | const results = await class3.calculateBookPrice()
154 | let cur = new Intl.NumberFormat(getLocaleReq(req), { style: "currency", currency: results.currency })
155 | return res.type("text/html").status(200).send(
156 | `Call Instance Method - Calc Price: ${cur.format(results.price)}`)
157 | } catch (e) {
158 | app.log.error(e)
159 | let error = new cds.error(e)
160 | return next(error)
161 | }
162 | })
163 |
164 | /**
165 | * @swagger
166 | *
167 | * /rest/oo/classes4a:
168 | * get:
169 | * summary: OO - Call Inherited Method
170 | * tags:
171 | * - oo
172 | * responses:
173 | * '200':
174 | * description: Output
175 | */
176 | app.get("/rest/oo/classes4a", async(req, res, next) => {
177 | try {
178 | let class4 = new ooTutorial4("Wuthering Heights")
179 | const results = await class4.getBookDetails()
180 | return res.type("text/html").status(200).send(
181 | `Call Inherited Method: ${JSON.stringify(results)}`)
182 | } catch (e) {
183 | app.log.error(e)
184 | let error = new cds.error(e)
185 | return next(error)
186 | }
187 | })
188 |
189 | /**
190 | * @swagger
191 | *
192 | * /rest/oo/classes4b:
193 | * get:
194 | * summary: OO - Call Overridden Method
195 | * tags:
196 | * - oo
197 | * responses:
198 | * '200':
199 | * description: Output
200 | */
201 | app.get("/rest/oo/classes4b", async(req, res, next) => {
202 | try {
203 | let class4 = new ooTutorial4("Wuthering Heights")
204 | const results = await class4.calculateBookPrice()
205 | let cur = new Intl.NumberFormat(getLocaleReq(req), { style: "currency", currency: results.currency })
206 | return res.type("text/html").status(200).send(
207 | `Call Overridden Method - Calc Price: ${cur.format(results.price)}`)
208 | } catch (e) {
209 | app.log.error(e)
210 | let error = new cds.error(e)
211 | return next(error)
212 | }
213 | })
214 |
215 | return app
216 | }
--------------------------------------------------------------------------------
/srv/routes/zip.js:
--------------------------------------------------------------------------------
1 | import zipClass from 'node-zip'
2 | import toc from '../utils/exampleTOC.js'
3 |
4 | export function load(app) {
5 |
6 | app.get("/rest/zip", (req, res) => {
7 | var output =
8 | `ZIP Examples
9 | /example1 - Download data in ZIP format - folder and files
10 | /zipBooks - Download Books in ZIP format
11 | /zipBooks2 - Download Books in ZIP format with selection inner format` +
12 | toc()
13 | res.type("text/html").status(200).send(output)
14 | })
15 |
16 | /**
17 | * @swagger
18 | *
19 | * /rest/zip/example1:
20 | * get:
21 | * summary: Manually Assemble content into Zip
22 | * tags:
23 | * - zip
24 | * responses:
25 | * '200':
26 | * description: Zip File
27 | * content:
28 | * application/zip:
29 | * schema:
30 | * type: string
31 | * format: binary
32 | * '500':
33 | * description: General DB Error
34 | */
35 | app.get("/rest/zip/example1", (req, res) => {
36 | const zip = new zipClass()
37 | zip.file("folder1/demo1.txt", "This is the new ZIP Processing in Node.js")
38 | zip.file("demo2.txt", "This is also the new ZIP Processing in Node.js")
39 | let data = zip.generate({
40 | base64: false,
41 | compression: "DEFLATE"
42 | })
43 | res.header("Content-Disposition", "attachment; filename=ZipExample.zip")
44 | return res.type("application/zip").status(200).send(Buffer.from(data, "binary"))
45 | })
46 |
47 | /**
48 | * @swagger
49 | *
50 | * /rest/zip/zipBooks:
51 | * get:
52 | * summary: DB query results in Zip
53 | * tags:
54 | * - zip
55 | * responses:
56 | * '200':
57 | * description: Zip File
58 | * content:
59 | * application/zip:
60 | * schema:
61 | * type: string
62 | * format: binary
63 | * '500':
64 | * description: General DB Error
65 | */
66 | app.get("/rest/zip/zipBooks", async (req, res, next) => {
67 | try {
68 | let dbQuery = SELECT
69 | .from(cds.entities.Books)
70 | .columns("ID", "title", "stock", "price", "currency_code", "author.name as author")
71 | .limit(25)
72 | const results = await cds.run(dbQuery)
73 | let out = `ID\tTitle\tAuthor\tStock\tPrice\tCurrency\n`
74 | for (let item of results) {
75 | out +=
76 | `${item.ID}\t${item.title}\t${item.author}\t${item.stock}\t${item.price}\t${item.currency_code}\n`
77 | }
78 | const zip = new zipClass()
79 | zip.file("books.txt", out)
80 | let data = zip.generate({
81 | base64: false,
82 | compression: "DEFLATE"
83 | })
84 | res.header("Content-Disposition", "attachment; filename=bookWorklist.zip")
85 | return res.type("application/zip").status(200).send(Buffer.from(data, "binary"))
86 | } catch (e) {
87 | app.log.error(e)
88 | let error = new cds.error(e)
89 | return next(error)
90 | }
91 | })
92 |
93 | /**
94 | * @swagger
95 | *
96 | * /rest/zip/zipBooks2/{format}:
97 | * get:
98 | * summary: DB query results in Zip - Multiple Inner Formats
99 | * tags:
100 | * - zip
101 | * parameters:
102 | * - name: format
103 | * in: path
104 | * description: output format
105 | * required: false
106 | * default: txt
107 | * schema:
108 | * type: string
109 | * enum: [txt, json, xlsx, csv]
110 | * responses:
111 | * '200':
112 | * description: Zip File
113 | * content:
114 | * application/zip:
115 | * schema:
116 | * type: string
117 | * format: binary
118 | * '500':
119 | * description: General DB Error
120 | */
121 | app.get("/rest/zip/zipBooks2/:format?", async (req, res, next) => {
122 | try {
123 | let dbQuery = SELECT
124 | .from(cds.entities.Books)
125 | .columns("ID", "title", "stock", "price", "currency_code", "author.name as author")
126 | .limit(25)
127 | const results = await cds.run(dbQuery)
128 | let format = req.params.format
129 | if (typeof format === "undefined" || format === null) {
130 | format = 'txt'
131 | }
132 |
133 | let output
134 | switch (format) {
135 | case 'xlsx':
136 | output = to_zip(await excel(results), format)
137 | break
138 | case 'json':
139 | output = to_zip(JSON.stringify(results, null, 2), format)
140 | break
141 | case 'csv':
142 | output = to_zip(await csv(results), format)
143 | break
144 | case 'txt':
145 | output = to_zip(await text(results), format)
146 | break
147 | case 'default':
148 | throw new Error('Unknown format')
149 | }
150 | res.header("Content-Disposition", "attachment; filename=bookWorklist.zip")
151 | return res.type("application/zip").status(200).send(Buffer.from(output, "binary"))
152 | } catch (e) {
153 | app.log.error(e)
154 | let error = new cds.error(e)
155 | return next(error)
156 | }
157 | })
158 |
159 | function to_zip(data, format) {
160 | const zip = new zipClass()
161 | zip.file(`books.${format}`, data)
162 | let dataOut = zip.generate({
163 | base64: false,
164 | compression: "DEFLATE"
165 | })
166 | return dataOut
167 | }
168 |
169 | async function excel(results) {
170 | const { default: excel } = await import('node-xlsx')
171 | let out = []
172 | //Column Headers
173 | let header = []
174 | for (const [key] of Object.entries(results[0])) {
175 | header.push(key)
176 | }
177 | out.push(header)
178 |
179 | for (let item of results) {
180 | let innerItem = []
181 | for (const [key] of Object.entries(item)) {
182 | innerItem.push(item[key])
183 | }
184 | out.push(innerItem)
185 | }
186 | // @ts-ignore
187 | let excelOutput = excel.build([{
188 | name: "Books",
189 | data: out
190 | }])
191 | return excelOutput
192 | }
193 |
194 | async function csv(results) {
195 | const {AsyncParser} = await import('@json2csv/node')
196 | const opts = { delimiter: ";", transforms: [removeNewlineCharacter] }
197 | const transformOpts = {}
198 | const asyncOpts = {}
199 | const parser = new AsyncParser(opts, transformOpts, asyncOpts)
200 | return await parser.parse(results).promise()
201 | }
202 |
203 | async function text(results){
204 | const {default: Table} = await import('easy-table')
205 | return Table.print(results)
206 | }
207 |
208 | function removeNewlineCharacter(dataRow) {
209 |
210 | let newDataRow = {}
211 | Object.keys(dataRow).forEach((key) => {
212 | if (typeof dataRow[key] === "string") {
213 | newDataRow[key] = dataRow[key].replace(/[\n\r]+/g, ' ')
214 | } else {
215 | newDataRow[key] = dataRow[key];
216 | }
217 | })
218 | return newDataRow
219 | }
220 |
221 | return app
222 | }
--------------------------------------------------------------------------------
/srv/routes/es6.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-inner-declarations */
2 | import util from 'util'
3 | import fs from 'fs'
4 | import { fileURLToPath } from 'url'
5 | const __dirname = fileURLToPath(new URL('.', import.meta.url))
6 | import * as path from 'path'
7 |
8 | export function load(app) {
9 |
10 |
11 | //New Node.js 8.x Utility to Promisify for you if the target uses (err,value)
12 | const readFilePromisified = util.promisify(fs.readFile)
13 |
14 |
15 | //Hello Router
16 | app.get("/rest/es6", (req, res) => {
17 | let output =
18 | `ES6 Features
19 | /promises - Manual Promisefy readFile
20 | /promisesError - Manual Promisefy readFile and catch error
21 | /constants - Constants
22 | /blockScoped - Block-Scoped Variables and Functions
23 | /parameterDefaults - Parameter Defaults
24 | /parameterMultiple - Handling unknown number of input parameters easily
25 | /unicode - Unicode Strings and Literals
26 | /numFormat - International Number Formatting
27 | /currFormat - International Currency Formatting
28 | /dateFormat - International Date/Time Formatting`
29 | res.type("text/html").status(200).send(output)
30 | })
31 |
32 | /**
33 | * @swagger
34 | *
35 | * /rest/promises/promises:
36 | * get:
37 | * summary: Manual Promisify readFile
38 | * tags:
39 | * - promises
40 | * responses:
41 | * '200':
42 | * description: Output
43 | */
44 | app.get("/rest/es6/promises", (req, res, next) => {
45 | readFilePromisified(path.join(__dirname, "../async/file.txt"))
46 | .then(text => {
47 | return res.type("text/html").status(200).send(text)
48 | })
49 | .catch(e => {
50 | app.log.error(e)
51 | let error = new cds.error(e)
52 | return next(error)
53 | })
54 | })
55 |
56 | /**
57 | * @swagger
58 | *
59 | * /rest/promises/promisesError:
60 | * get:
61 | * summary: Manual Promisify readFile and catch error
62 | * tags:
63 | * - promises
64 | * responses:
65 | * '200':
66 | * description: Output
67 | */
68 | app.get("/rest/es6/promisesError", (req, res, next) => {
69 | readFilePromisified(path.join(__dirname, "../async/missing.txt"))
70 | .then(text => {
71 | return res.type("text/html").status(200).send(text)
72 | })
73 | .catch(e => {
74 | app.log.error(e)
75 | let error = new cds.error(e)
76 | return next(error)
77 | })
78 | })
79 |
80 | /**
81 | * @swagger
82 | *
83 | * /rest/es6/constants:
84 | * get:
85 | * summary: ES6 Constants
86 | * tags:
87 | * - es6
88 | * responses:
89 | * '200':
90 | * description: Output
91 | */
92 | app.get("/rest/es6/constants", (req, res, next) => {
93 | const fixVal = 10
94 | let newVal = fixVal
95 | try {
96 | newVal++
97 | // eslint-disable-next-line no-const-assign
98 | fixVal++
99 | } catch (e) {
100 |
101 | app.log.error(e)
102 | let error = new cds.error(`Constant Value: ${fixVal.toString()}, Copied Value: ${newVal.toString()}, Error: ${e.toString()}`)
103 | return next(error)
104 | }
105 | })
106 |
107 | /**
108 | * @swagger
109 | *
110 | * /rest/es6/blockScoped:
111 | * get:
112 | * summary: Block Scoped
113 | * tags:
114 | * - es6
115 | * responses:
116 | * '200':
117 | * description: Output
118 | */
119 | app.get("/rest/es6/blockScoped", (req, res) => {
120 | let output
121 | function foo() {
122 | return 1
123 | }
124 | output = `Outer function result: ${foo()} `
125 | if (foo() === 1) {
126 | function foo() {
127 | return 2
128 | }
129 | output += `Inner function results: ${foo()}`
130 | }
131 | return res.type("text/html").status(200).send(output)
132 | })
133 |
134 | /**
135 | * @swagger
136 | *
137 | * /rest/es6/parameterDefaults:
138 | * get:
139 | * summary: Parameter Defaults
140 | * tags:
141 | * - es6
142 | * responses:
143 | * '200':
144 | * description: Output
145 | */
146 | app.get("/rest/es6/parameterDefaults", (req, res) => {
147 | function math(a, b = 10, c = 12) {
148 | return a + b + c
149 | }
150 | return res.type("text/html").status(200).send(`With Defaults: ${math(5)}, With supplied values: ${math(5, 1, 1)}`)
151 |
152 | })
153 |
154 | /**
155 | * @swagger
156 | *
157 | * /rest/es6/parameterMultiple:
158 | * get:
159 | * summary: Multiple Parameter
160 | * tags:
161 | * - es6
162 | * responses:
163 | * '200':
164 | * description: Output
165 | */
166 | app.get("/rest/es6/parameterMultiple", (req, res) => {
167 | function getLength(a, b, ...p) {
168 | return a + b + p.length
169 | }
170 | return res.type("text/html").status(200).send(`2 plus 4 parameters: ${getLength(1, 1, "stuff", "More Stuff", 8, "last param")}`)
171 |
172 | })
173 |
174 | /**
175 | * @swagger
176 | *
177 | * /rest/es6/unicode:
178 | * get:
179 | * summary: Unicode Strings and Literals
180 | * tags:
181 | * - es6
182 | * responses:
183 | * '200':
184 | * description: Output
185 | */
186 | app.get("/rest/es6/unicode", (req, res) => {
187 | if ("𠮷".length === 2) {
188 | return res.type("text/html").status(200).send(`Output: ${"𠮷".toString()}, Code Points: ${"𠮷".codePointAt(0)}`)
189 | }
190 | })
191 |
192 | /**
193 | * @swagger
194 | *
195 | * /rest/es6/numFormat:
196 | * get:
197 | * summary: Number Format
198 | * tags:
199 | * - es6
200 | * responses:
201 | * '200':
202 | * description: Output
203 | */
204 | app.get("/rest/es6/numFormat", (req, res) => {
205 | let numEN = new Intl.NumberFormat("en-US")
206 | let numDE = new Intl.NumberFormat("de-DE")
207 | return res.type("text/html").status(200).send(`US: ${numEN.format(123456789.10)}, DE: ${numDE.format(123456789.10)}`)
208 | })
209 |
210 | /**
211 | * @swagger
212 | *
213 | * /rest/es6/currFormat:
214 | * get:
215 | * summary: Currency Formatting
216 | * tags:
217 | * - es6
218 | * responses:
219 | * '200':
220 | * description: Output
221 | */
222 | app.get("/rest/es6/currFormat", (req, res) => {
223 | let curUS = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
224 | let curDE = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" })
225 | return res.type("text/html").status(200).send(`US: ${curUS.format(123456789.10)}, DE: ${curDE.format(123456789.10)}`)
226 | })
227 |
228 | /**
229 | * @swagger
230 | *
231 | * /rest/es6/dateFormat:
232 | * get:
233 | * summary: Date/Time Formatting
234 | * tags:
235 | * - es6
236 | * responses:
237 | * '200':
238 | * description: Output
239 | */
240 | app.get("/rest/es6/dateFormat", (req, res) => {
241 | let dateUS = new Intl.DateTimeFormat("en-US")
242 | let dateDE = new Intl.DateTimeFormat("de-DE")
243 | return res.type("text/html").status(200).send(`US: ${dateUS.format(new Date())}, DE: ${dateDE.format(new Date())}`)
244 | })
245 |
246 | return app
247 | }
--------------------------------------------------------------------------------
/srv/routes/es2018.js:
--------------------------------------------------------------------------------
1 | import toc from '../utils/exampleTOC.js'
2 | import util from 'util'
3 | import fs from 'fs'
4 |
5 | export function load(app) {
6 | const readFilePromisified = util.promisify(fs.readFile)
7 |
8 | //Hello Router
9 | app.get("/rest/es2018", (req, res) => {
10 | let output =
11 | `ES6 Features
12 | /promisesFinally - promisesFinally
13 | /for-await-of-loops - for-await-of-loops
14 | /async-generator - async-generator
15 | /regex2018 - regex2018` +
16 | toc()
17 | res.type("text/html").status(200).send(output)
18 | })
19 |
20 | /**
21 | * @swagger
22 | *
23 | * /rest/es2018/promisesFinally:
24 | * get:
25 | * summary: Demonstrates the use of Promise.prototype.finally(), after a promise is settled finally() is called. Finally() does not receive an argument
26 | * tags:
27 | * - es2018
28 | * responses:
29 | * '200':
30 | * description: Output
31 | */
32 | app.get("/rest/es2018/promisesFinally", async (req, res) => {
33 | let output = ""
34 | let fileName = "async/missing.txt"
35 | await readFilePromisified(global.__base + fileName)
36 | .then(text => {
37 | return text
38 | })
39 | .catch(error => {
40 | output += "First file error: " + error + ' '
41 | return error.toString()
42 | })
43 | .finally(() => {
44 | output += "First promise settled, changing file name." + ' '
45 | fileName = "async/file.txt"
46 | return
47 | })
48 |
49 | await readFilePromisified(global.__base + fileName)
50 | .then(text => {
51 | output += "Second file success: " + text + ' '
52 | return
53 | })
54 | .catch(error => {
55 | return error.toString()
56 | })
57 | .finally(() => {
58 | output += "Second promise settled." + ' '
59 | return
60 | })
61 |
62 | res.type("text/html").status(200).send(output)
63 | })
64 |
65 | /**
66 | * @swagger
67 | *
68 | * /rest/es2018/for-await-of-loops:
69 | * get:
70 | * summary: Demo of For Await Loops
71 | * tags:
72 | * - es2018
73 | * responses:
74 | * '200':
75 | * description: Output
76 | */
77 | app.get("/rest/es2018/for-await-of-loops", async (req, res) => {
78 | let output = ""
79 | let i = 0
80 | let fileNames = ["async/file.txt", "async/file2.txt"]
81 | const asyncIterator = {
82 | next: async () => {
83 | let fileText = ""
84 | if (i === fileNames.length) {
85 | return {
86 | done: true
87 | }
88 | }
89 | await readFilePromisified(global.__base + fileNames[i])
90 | .then(text => {
91 | fileText = text
92 | })
93 | i++
94 | return Promise.resolve({
95 | value: fileText,
96 | done: false
97 | })
98 | }
99 | }
100 |
101 | const asyncIterable = {
102 | [Symbol.asyncIterator]: () => asyncIterator
103 | }
104 | for await (const value of asyncIterable) {
105 | output += value.toString() + " "
106 | }
107 |
108 | res.type("text/html").status(200).send(output)
109 |
110 | })
111 |
112 | /**
113 | * @swagger
114 | *
115 | * /rest/es2018/async-generator:
116 | * get:
117 | * summary: Async generator function returns Object [AsyncGenerator]
118 | * tags:
119 | * - es2018
120 | * responses:
121 | * '200':
122 | * description: Output
123 | */
124 | app.get("/rest/es2018/async-generator", async (req, res) => {
125 | let output = ""
126 | let i = 0
127 |
128 | //Async generator function returns Object [AsyncGenerator]
129 | //Calling .next() returns a Promise that resolves to {value, done}
130 | async function* getFileText(fileNames) {
131 | var fileText = ""
132 | while (true) {
133 | if (i === 2) break
134 | await readFilePromisified(global.__base + fileNames[i])
135 | .then(text => {
136 | fileText = text
137 | })
138 | i++
139 | yield fileText
140 | }
141 | }
142 |
143 | let fileText = {
144 | fileTextGenerator: await getFileText(["async/file.txt", "async/file2.txt"]),
145 | getNextFile: async function () {
146 | var next = await this.fileTextGenerator.next()
147 | var value = next.value
148 | return value.toString('utf8')
149 | }
150 | }
151 | output += "First file text: " + await fileText.getNextFile() + "
"
152 | output += "Second file text: " + await fileText.getNextFile() + "
"
153 |
154 | res.type("text/html").status(200).send(output)
155 | })
156 |
157 | /**
158 | * @swagger
159 | *
160 | * /rest/es2018/regex2018:
161 | * get:
162 | * summary: Regexp additions
163 | * tags:
164 | * - es2018
165 | * responses:
166 | * '200':
167 | * description: Output
168 | */
169 | app.get("/rest/es2018/regex2018", (req, res) => {
170 |
171 | //START OF S FLAG
172 | let output = "ES2018's New RegExp Features "
173 | output +=
174 | "
1. s (dotAll) flag allows the '.' to match new line characters. Below are the results of using the expression /^.*/ on a string with and without the 's' flag "
175 | let testStr = "This is my string \n this part is on a new line"
176 |
177 | let RE = /^.*/ //match all
178 | let RE2 = /^.*/s //match all with 's' flag
179 | output += "
"
180 | output += "The string:
This is my string this part is on a new line
"
181 | output += "Results without dotAll flag: " + testStr.match(RE) + "
"
182 | output += "Results using dotAll flag: "
183 | output += testStr.match(RE2) + "
"
184 | output += " "
185 |
186 | //START OF NAME CAPTURE GROUPS
187 | output +=
188 | "
2. Name Captured Groups allows you to capture sections of a string and store them inside a match object by name: "
189 | //Matches multiple sets of digits between hyphens and assigns them to names year, month, day
190 | const RE_DATE = /(?
[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/
191 | const matchObj = "1997-08-12".match(RE_DATE)
192 | const year = matchObj.groups.year
193 | const month = matchObj.groups.month
194 | const day = matchObj.groups.day
195 | output += ""
196 | output += "String: 1997-08-12
"
197 | output += "Year: " + year + "
Month: " + month + "
Day: " + day +
198 | "
"
199 | output +=
200 | "Name capture groups also allow for backreferences. Backreferences allow for matching a name captured group making it easier to match repeated patterns: "
201 | const RE_BACKREF = /(?[a-zA-Z]{3})[:]\k/
202 | output += "String: cat:cat
"
203 | var backRefStr = "cat:cat"
204 | output += backRefStr.match(RE_BACKREF)
205 | output +=
206 | "Name capture groups can also be used within the replace() method for doing tasks like changing our date string's order: "
207 | output += "1997-08-12".replace(RE_DATE, "$/$/$") //uses name groups to swap the date order
208 | output += " "
209 |
210 | //START OF LOOK BEHINDS
211 | output +=
212 | "3. Look Behind Assertions allows you to match sections of a string by setting a condition the preceding characters: "
213 | output += "Our regex looks for the 'text' string that is preceded by 'REPLACE: '
"
214 | output += "Starting string: " + "REPLACE: text, LEAVE: text
"
215 | output += "Ending string: " + "REPLACE: text, LEAVE: text".replace(/(?<=REPLACE: )text/, "new text") + "
"
216 |
217 | output += "We can also use \"Negative Lookbehinds\". Our regex looks for the 'text' string that is NOT preceded by 'REPLACE: '
"
218 | output += "Starting string: " + "REPLACE: text, LEAVE: text
"
219 | output += "Ending string: " + "REPLACE: text, LEAVE: text".replace(/(?"
220 | output += "
"
221 |
222 | //START OF UNICODE PROPERTY ESCAPES
223 | output +=
224 | "4. Unicode Property Escapes allows you to match characters using their Unicode character properties. "
225 | var unicodeString = "!@#$%^&*"
226 | output += " "
237 | res.type("text/html").status(200).send(output)
238 | })
239 |
240 | return app
241 | }
--------------------------------------------------------------------------------
/srv/routes/ex2.js:
--------------------------------------------------------------------------------
1 | import toc from '../utils/exampleTOC.js'
2 | import asyncLib from 'async'
3 | import { getSafe } from '../utils/general.js'
4 | import os from 'os'
5 | import child_process from 'child_process'
6 |
7 | export function load(app) {
8 |
9 | let dbQuery = ""
10 | switch (cds.env.requires.db.kind) {
11 | case 'hana':
12 | `SELECT TABLE_NAME FROM M_TABLES
13 | WHERE SCHEMA_NAME = CURRENT_SCHEMA
14 | AND RECORD_COUNT > 0 `
15 |
16 | dbQuery = SELECT
17 | .columns("TABLE_NAME")
18 | .from("M_TABLES")
19 | .where({RECORD_COUNT: {'>': 0}})
20 | .limit(50)
21 | break
22 | case 'sqlite':
23 | dbQuery = SELECT
24 | .columns("type", "name", "tbl_name")
25 | .from("sqlite_schema")
26 | .limit(50)
27 | break
28 | case 'pg':
29 | dbQuery = SELECT
30 | .columns("schemaname", "tablename")
31 | .from("pg_catalog.pg_tables")
32 | .limit(50)
33 | break
34 | default:
35 | break
36 | }
37 |
38 | app.get("/rest/ex2/toc", (req, res) => {
39 | let output =
40 | `Exercise #2
41 | / - DB Client
42 | /waterfall - Simple Database Select Via Client Wrapper/Middelware - Async Waterfall
43 | /promises - Simple Database Select Via Client Wrapper/Middelware - Promises
44 | /await - Simple Database Select Via Client Wrapper/Middelware - Await
45 | /queryParallel - Call 2 Database queries in Parallel
46 | /whoAmI - Current User Info
47 | /env - Environment Info
48 | /cfApi - Current Cloud Foundry API
49 | /space - Current Space
50 | /os - OS Info
51 | /osUser - OS User` +
52 | toc()
53 | res.type("text/html").status(200).send(output)
54 | })
55 |
56 | /**
57 | * @swagger
58 | *
59 | * /rest/ex2:
60 | * get:
61 | * summary: DB Example with Callbacks and the hana-client
62 | * tags:
63 | * - exercises
64 | * responses:
65 | * '200':
66 | * description: Output
67 | */
68 | app.get("/rest/ex2", async (req, res, next) => {
69 | try {
70 | let results = await cds.run(dbQuery)
71 | return res.type("application/json").status(200).send(results)
72 | } catch (e) {
73 | app.log.error(e)
74 | let error = new cds.error(e)
75 | return next(error)
76 | }
77 | })
78 |
79 | /**
80 | * @swagger
81 | *
82 | * /rest/ex2/waterfall:
83 | * get:
84 | * summary: Simple Database Select Via Client Wrapper/Middelware - Async Waterfall
85 | * tags:
86 | * - exercises
87 | * responses:
88 | * '200':
89 | * description: Output
90 | */
91 | app.get("/rest/ex2/waterfall", (req, res, next) => {
92 | asyncLib.waterfall([
93 | function execute(callback) {
94 | cds.run(dbQuery)
95 | .then(results => {
96 | callback(null, results)
97 | }).catch(error => {
98 | callback(error)
99 | })
100 | },
101 | function response(results, callback) {
102 | let result = JSON.stringify({
103 | Objects: results
104 | })
105 | res.type("application/json").status(200).send(result)
106 | return callback()
107 | }
108 | ], function (e) {
109 | if (e) {
110 | app.log.error(e)
111 | let error = new cds.error(e)
112 | return next(error)
113 | }
114 | })
115 | })
116 |
117 | /**
118 | * @swagger
119 | *
120 | * /rest/ex2/promises:
121 | * get:
122 | * summary: Simple Database Select Via Client Wrapper/Middelware - Promises
123 | * tags:
124 | * - exercises
125 | * responses:
126 | * '200':
127 | * description: Output
128 | */
129 | app.get("/rest/ex2/promises", (req, res, next) => {
130 |
131 | cds.run(dbQuery)
132 | .then(results => {
133 | return res.type("application/json").status(200).send(results)
134 | }).catch(e => {
135 | app.log.error(e)
136 | let error = new cds.error(e)
137 | return next(error)
138 | })
139 |
140 | })
141 |
142 | /**
143 | * @swagger
144 | *
145 | * /rest/ex2/await:
146 | * get:
147 | * summary: Simple Database Select Via Client Wrapper/Middelware - Await
148 | * tags:
149 | * - exercises
150 | * responses:
151 | * '200':
152 | * description: Output
153 | */
154 | app.get("/rest/ex2/await", async (req, res, next) => {
155 | try {
156 | let results = await cds.run(dbQuery)
157 | return res.type("application/json").status(200).send(results)
158 | } catch (e) {
159 | app.log.error(e)
160 | let error = new cds.error(e)
161 | return next(error)
162 | }
163 | })
164 |
165 | /**
166 | * @swagger
167 | *
168 | * /rest/ex2/queryParallel:
169 | * get:
170 | * summary: Call 2 Database Queries in Parallel
171 | * tags:
172 | * - exercises
173 | * responses:
174 | * '200':
175 | * description: Output
176 | */
177 | app.get("/rest/ex2/queryParallel", async (req, res, next) => {
178 | try {
179 | let [results1, results2] = await Promise.all([
180 | cds.run(dbQuery),
181 | cds.run(dbQuery)
182 | ])
183 | let output = { "results1": results1, "results2": results2 }
184 | return res.type("application/json").status(200).send(output)
185 | } catch (e) {
186 | app.log.error(e)
187 | let error = new cds.error(e)
188 | return next(error)
189 | }
190 | })
191 |
192 | /**
193 | * @swagger
194 | *
195 | * /rest/ex2/whoAmI:
196 | * get:
197 | * summary: User Context Information
198 | * tags:
199 | * - exercises
200 | * responses:
201 | * '200':
202 | * description: Output
203 | */
204 | app.get("/rest/ex2/whoAmI", (req, res) => {
205 | function mapSecurityContext(context) {
206 | let output = {}
207 | if (context) {
208 | output.user = {}
209 | output.user.logonName = getSafe(context.getLogonName())
210 | output.user.givenName = getSafe(context.getGivenName())
211 | output.user.familyName = getSafe(context.getFamilyName())
212 | output.user.email = getSafe(context.getEmail())
213 | output.user.userName = getSafe(context.getUserName())
214 | output.grantType = getSafe(context.getGrantType())
215 | output.uniquePrincipalName = getSafe(context.getUniquePrincipalName())
216 | output.origin = getSafe(context.getOrigin())
217 | output.appToken = getSafe(context.getAppToken())
218 | output.hdbToken = getSafe(context.getHdbToken())
219 | output.isInForeignMode = getSafe(context.isInForeignMode())
220 | output.subdomain = getSafe(context.getSubdomain())
221 | output.clientId = getSafe(context.getClientId())
222 | output.subaccountId = getSafe(context.getSubaccountId())
223 | output.expirationDate = getSafe(context.getExpirationDate())
224 | output.cloneServiceInstanceId = getSafe(context.getCloneServiceInstanceId())
225 | }
226 | return output
227 | }
228 | let userContext = req.authInfo
229 | let result = JSON.stringify({
230 | userContext: mapSecurityContext(userContext)
231 | })
232 | res.type("application/json").status(200).send(result)
233 | })
234 |
235 | /**
236 | * @swagger
237 | *
238 | * /rest/ex2/env:
239 | * get:
240 | * summary: Process Environment
241 | * tags:
242 | * - exercises
243 | * responses:
244 | * '200':
245 | * description: Output
246 | */
247 | app.get("/rest/ex2/env", (req, res) => {
248 | return res.type("application/json").status(200).send(JSON.stringify(process.env))
249 | })
250 |
251 | /**
252 | * @swagger
253 | *
254 | * /rest/ex2/cfApi:
255 | * get:
256 | * summary: VCAP CF API Endpoint
257 | * tags:
258 | * - exercises
259 | * responses:
260 | * '200':
261 | * description: Output
262 | */
263 | app.get("/rest/ex2/cfApi", (req, res) => {
264 | let VCAP = JSON.parse(process.env.VCAP_APPLICATION || `{"cf_api": "No VCAP_APPLICATION present in the environment"}`)
265 | return res.type("application/json").status(200).send(JSON.stringify(VCAP.cf_api))
266 | })
267 |
268 | /**
269 | * @swagger
270 | *
271 | * /rest/ex2/space:
272 | * get:
273 | * summary: VCAP Space
274 | * tags:
275 | * - exercises
276 | * responses:
277 | * '200':
278 | * description: Output
279 | */
280 | app.get("/rest/ex2/space", (req, res) => {
281 | let VCAP = JSON.parse(process.env.VCAP_APPLICATION || `{"space_name": "No VCAP_APPLICATION present in the environment"}`)
282 | return res.type("application/json").status(200).send(JSON.stringify(VCAP.space_name))
283 | })
284 |
285 | /**
286 | * @swagger
287 | *
288 | * /rest/ex2/os:
289 | * get:
290 | * summary: Simple call to the Operating System
291 | * tags:
292 | * - exercises
293 | * responses:
294 | * '200':
295 | * description: Output
296 | */
297 | app.get("/rest/ex2/os", (req, res) => {
298 | let output = {}
299 |
300 | output.tmpdir = os.tmpdir()
301 | output.endianness = os.endianness()
302 | output.hostname = os.hostname()
303 | output.type = os.type()
304 | output.platform = os.platform()
305 | output.arch = os.arch()
306 | output.release = os.release()
307 | output.uptime = os.uptime()
308 | output.loadavg = os.loadavg()
309 | output.totalmem = os.totalmem()
310 | output.freemem = os.freemem()
311 | output.cpus = os.cpus()
312 | output.networkInterfaces = os.networkInterfaces()
313 |
314 | var result = JSON.stringify(output)
315 | res.type("application/json").status(200).send(result)
316 | })
317 |
318 | /**
319 | * @swagger
320 | *
321 | * /rest/ex2/osUser:
322 | * get:
323 | * summary: Return OS level User
324 | * tags:
325 | * - exercises
326 | * responses:
327 | * '200':
328 | * description: Output
329 | */
330 | app.get("/rest/ex2/osUser", (req, res, next) => {
331 | var exec = child_process.exec
332 | exec("whoami", (e, stdout) => {
333 | if (e) {
334 | app.log.error(e)
335 | let error = new cds.error(e)
336 | return next(error)
337 | } else {
338 | res.type("text/plain").status(200).send(stdout)
339 | }
340 | })
341 | })
342 |
343 | return app
344 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2024] [SAP SE]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSES/Apache-2.0.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2024] [SAP SE]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------