├── .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 | 10 | 11 |