@sap/cds 8.0.3 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/_i18n/i18n_bg.properties +113 -0
  3. package/_i18n/i18n_el.properties +113 -0
  4. package/_i18n/i18n_he.properties +113 -0
  5. package/_i18n/i18n_hr.properties +113 -0
  6. package/_i18n/i18n_kk.properties +113 -0
  7. package/_i18n/i18n_sh.properties +113 -0
  8. package/_i18n/i18n_sk.properties +113 -0
  9. package/_i18n/i18n_sl.properties +113 -0
  10. package/_i18n/i18n_uk.properties +113 -0
  11. package/lib/compile/etc/_localized.js +8 -20
  12. package/lib/dbs/cds-deploy.js +1 -0
  13. package/lib/env/cds-requires.js +1 -0
  14. package/lib/env/defaults.js +1 -1
  15. package/lib/env/plugins.js +22 -6
  16. package/lib/linked/validate.js +1 -1
  17. package/lib/log/cds-log.js +2 -2
  18. package/lib/srv/protocols/hcql.js +5 -5
  19. package/lib/srv/protocols/http.js +23 -11
  20. package/lib/test/expect.js +1 -1
  21. package/lib/utils/cds-test.js +4 -4
  22. package/libx/_runtime/common/composition/insert.js +1 -1
  23. package/libx/_runtime/common/error/utils.js +2 -1
  24. package/libx/_runtime/common/generic/input.js +2 -5
  25. package/libx/_runtime/common/generic/stream.js +18 -3
  26. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  27. package/libx/_runtime/db/query/read.js +18 -9
  28. package/libx/_runtime/fiori/lean-draft.js +2 -2
  29. package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +1 -1
  30. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  31. package/libx/_runtime/messaging/event-broker.js +76 -24
  32. package/libx/common/assert/utils.js +1 -57
  33. package/libx/odata/middleware/batch.js +86 -40
  34. package/libx/odata/middleware/body-parser.js +2 -3
  35. package/libx/odata/middleware/operation.js +11 -11
  36. package/libx/odata/middleware/read.js +1 -1
  37. package/libx/odata/parse/grammar.peggy +6 -1
  38. package/libx/odata/parse/parser.js +1 -1
  39. package/libx/odata/utils/metadata.js +18 -44
  40. package/libx/rest/middleware/error.js +9 -2
  41. package/libx/rest/middleware/parse.js +1 -1
  42. package/package.json +1 -1
  43. package/libx/common/assert/index.js +0 -228
  44. package/libx/common/assert/type-relaxed.js +0 -39
@@ -17,38 +17,50 @@ class HttpAdapter {
17
17
 
18
18
  /** The actual Router factory. Subclasses override this to add specific handlers. */
19
19
  get router() {
20
- let router = super.router = (new express.Router) .use (this.http_log.bind(this))
21
- let assert_roles = this.requires_check()
22
- if (assert_roles) router.use (assert_roles)
20
+ let router = super.router = (new express.Router)
21
+ this.use (this.http_log)
22
+ this.use (this.requires_check)
23
23
  return router
24
24
  }
25
25
 
26
- /** Handler to log all incoming requests */
27
- http_log (r,_,next) {
28
- this.logger = cds.log(this.kind)
29
- this.log(r)
30
- next()
26
+ use (middleware) {
27
+ if (middleware) this.router.use (middleware)
28
+ return this
31
29
  }
32
30
 
33
31
  /** Subclasses may override this method to log incoming requests. */
34
- log (req, LOG = this.logger) { LOG._info && LOG.info (
32
+ log (req, LOG = this.logger) { LOG.info (
35
33
  req.method,
36
34
  decodeURI (req.baseUrl + req.path),
37
35
  Object.keys (req.query).length ? { ...req.query } : ''
38
36
  )}
39
37
 
38
+ /** Returns a handler to log all incoming requests */
39
+ get http_log() {
40
+ const LOG = this.logger = cds.log(this.kind); if (!LOG._info) return undefined
41
+ const log = this.log.bind(this)
42
+ return function http_log (req,_,next) { log(req,LOG); next() }
43
+ }
44
+
40
45
  /** Returns a handler to check required roles, or null if no check required. */
41
- requires_check() {
46
+ get requires_check() {
42
47
  const d = this.service.definition
43
48
  const roles = d['@requires'] || d['@restrict']?.map(r => r.to).flat().filter(r => r)
44
49
  const required = !roles?.length ? restricted_by_default : Array.isArray(roles) ? roles : [roles]
45
- if (required) return function requires_check (req, res, next) {
50
+ return required && function requires_check (req, res, next) {
46
51
  const user = cds.context.user
47
52
  if (required.some(role => user.has(role))) return next()
48
53
  else if (user._is_anonymous) return next(401) // request login
49
54
  else throw Object.assign(new Error, { code: 403, reason: `User '${user.id}' is lacking required roles: [${required}]`, user, required })
50
55
  }
51
56
  }
57
+
58
+ get body_parser_options() {
59
+ let options = cds.env.server.body_parser
60
+ let limit = this.service.definition['@cds.server.body_parser.limit']
61
+ if (limit) options = { ...options, limit }
62
+ return super.body_parser_options = options
63
+ }
52
64
  }
53
65
 
54
66
 
@@ -112,7 +112,7 @@ class Core {
112
112
  if (is.string(a)) return a.includes(x)
113
113
  if (is.array(a)) return a.includes(x) || this._deep && a.some(o => compare(o, x, true))
114
114
  if (is.set(a)) return a.has(x)
115
- if (this._deep && is.object(a)) return compare(a, x, true)
115
+ if (is.object(a)) return compare(a, x, this._deep)
116
116
  }, _fail)
117
117
  }
118
118
 
@@ -160,7 +160,7 @@ class Test extends require('./axios') {
160
160
  `)}}
161
161
  }
162
162
  set expect(x) { super.expect = x }
163
- get expect() { return this.chai.expect }
163
+ get expect() { return _expect || this.chai.expect }
164
164
  get assert() { return this.chai.assert }
165
165
  get should() { return this.chai.should() }
166
166
  }
@@ -174,6 +174,7 @@ Object.setPrototypeOf (exports, Test.prototype)
174
174
 
175
175
 
176
176
  // Provide same global functions for jest and mocha
177
+ let _expect = undefined
177
178
  ;(function _support_jest_and_mocha() {
178
179
  const _global = p => Object.getOwnPropertyDescriptor(global,p)?.value
179
180
  const is_jest = _global('beforeAll')
@@ -185,7 +186,7 @@ Object.setPrototypeOf (exports, Test.prototype)
185
186
  global.afterAll = global.after = (msg,fn) => repl.on?.('exit',fn||msg)
186
187
  global.beforeEach = global.afterEach = ()=>{}
187
188
  global.describe = ()=>{}
188
- exports.expect = global.expect = require('../test/expect')
189
+ global.expect = _expect = require('../test/expect')
189
190
 
190
191
  } else if (is_mocha) { // it's mocha
191
192
 
@@ -219,8 +220,7 @@ Object.setPrototypeOf (exports, Test.prototype)
219
220
  global.afterAll = global.after = (msg,fn) => after(fn||msg)
220
221
  global.beforeEach = beforeEach
221
222
  global.afterEach = afterEach
222
- global.expect = require('../test/expect')
223
- exports.expect = global.expect
223
+ global.expect = _expect = require('../test/expect')
224
224
  suite ('<next>', ()=>{}) //> to signal the start of a test file
225
225
 
226
226
  }
@@ -82,7 +82,7 @@ const hasDeepInsert = (model, cqn) => {
82
82
  const getDeepInsertCQNs = (model, cqn) => {
83
83
  const into = (cqn.INSERT.into.ref && cqn.INSERT.into.ref[0]) || cqn.INSERT.into.name || cqn.INSERT.into
84
84
  const entityName = ensureNoDraftsSuffix(into)
85
- const draft = entityName !== into
85
+ const draft = entityName !== into || into.endsWith('.drafts') //lean draft
86
86
  const dataEntries = cqn.INSERT.entries ? deepCopy(cqn.INSERT.entries) : []
87
87
  const entity = model.definitions[entityName]
88
88
  const compositionTree = getCompositionTree({
@@ -12,7 +12,8 @@ const i18n = (...args) => {
12
12
  * @returns localized error message
13
13
  */
14
14
  function getErrorMessage(error, locale) {
15
- const txt = i18n(error.message || error.code || error.status || error.statusCode, locale, error.args)
15
+ const key = error.message || error.code || error.status || error.statusCode || '500'
16
+ const txt = i18n(key, locale, error.args)
16
17
  return txt || error.message || String(error.code || error.status || error.statusCode)
17
18
  }
18
19
 
@@ -349,15 +349,12 @@ const _getOperation = (req, service) => {
349
349
 
350
350
  function _actionFunctionHandler(req) {
351
351
  const operation = _getOperation(req, this)
352
- if (!operation || !operation.params) return
352
+ if (!operation) return
353
353
 
354
354
  const data = req.data || {}
355
355
 
356
- // REVISIT: skip for mtxs as their models contain invalidities (e.g., properties modeled as strings but provided as objects)
357
- const is_mtxs = operation.name.match(/^cds\.xt\./)
358
-
359
356
  // validate data
360
- if (cds.env.features.cds_validate && !is_mtxs) {
357
+ if (cds.env.features.cds_validate) {
361
358
  const assertOptions = { mandatories: true }
362
359
  let errs = cds.validate(data, operation, assertOptions)
363
360
  if (errs) {
@@ -1,4 +1,14 @@
1
1
  const cds = require('../../cds')
2
+ // REVISIT: Remove after removing okra
3
+ const { isStreaming } = require('../../cds-services/adapter/odata-v4/utils/stream')
4
+
5
+ const _isStream = query => {
6
+ const { _propertyAccess, target } = query
7
+ if (!_propertyAccess) return
8
+
9
+ const element = target.elements[_propertyAccess]
10
+ return element._type === 'cds.LargeBinary' && element['@Core.MediaType']
11
+ }
2
12
 
3
13
  const _getStreamingProperties = elements => {
4
14
  const result = []
@@ -14,9 +24,7 @@ const _getStreamingProperties = elements => {
14
24
 
15
25
  const _getMediaTypeValue = () => {
16
26
  const ctx = cds.context
17
- return (
18
- !ctx?.http?.req?.headers?.['content-type']?.match(/json|multipart/i) && ctx?.http?.req?.headers?.['content-type']
19
- )
27
+ return !ctx?.http?.req?.headers?.['content-type']?.match(/multipart/i) && ctx?.http?.req?.headers?.['content-type']
20
28
  }
21
29
 
22
30
  function _addContentType(req, mtValue) {
@@ -27,12 +35,19 @@ function _addContentType(req, mtValue) {
27
35
 
28
36
  async function addContentType(req) {
29
37
  if (!req.query || !req.target) return
38
+ if (req._.odataReq) {
39
+ if (!isStreaming(req._.odataReq.getUriInfo().getPathSegments())) return
40
+ } else if (req.req?._query) {
41
+ if (!_isStream(req.req._query)) return
42
+ }
43
+
30
44
  const mtValue = _getMediaTypeValue()
31
45
  if (!mtValue) return
32
46
 
33
47
  _addContentType(req, mtValue)
34
48
  }
35
49
 
50
+ // register after input.js in order to write content-type also for @Core.Computed fields
36
51
  module.exports = cds.service.impl(function () {
37
52
  this.before(['PATCH', 'UPDATE'], '*', addContentType)
38
53
  })
@@ -758,13 +758,16 @@ const _convertSelect = (query, model, _options) => {
758
758
  // old db expects it as cqn xpr
759
759
  if (query.SELECT.search.length === 1) {
760
760
  query.SELECT.search = query.SELECT.search[0].val
761
- .replace(/"/g, '')
762
- .split(' ')
761
+ .match(/("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/g)
762
+ .filter(el => el.length)
763
+ .map(el => (el.match(/^["](.*)["]$/) ? JSON.parse(el) : el))
763
764
  .reduce((arr, val, i) => {
764
765
  if (i > 0) arr.push('and')
765
766
  arr.push({ val })
766
767
  return arr
767
768
  }, [])
769
+
770
+ if (!query.SELECT.search.length) query.SELECT.search = [{ val: '' }]
768
771
  }
769
772
 
770
773
  search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
@@ -60,17 +60,26 @@ const read = (executeSelectCQN, executeStreamCQN, convertStreams) => (model, dbc
60
60
 
61
61
  if (query.SELECT.count) {
62
62
  if (query.SELECT.limit) {
63
+ // IMPORTANT: do not change order!
64
+ // 1. create the count query synchronously, because it works on a copy
65
+ // 2. run the result query, duplicate names ( SELECT ID, ID ...) throw an error synchronously
66
+ // 3. run the count query
67
+ // reason is that executeSelectCQN modifies the query
63
68
  const countQuery = _createCountQuery(query)
64
- const countResultPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
65
- if (query.SELECT.limit?.rows?.val === 0) {
66
- // We don't need to perform our result query
67
- return countResultPromise.then(countResults => _arrayWithCount([], countValue(countResults)))
68
- }
69
+ const resultPromise =
70
+ query.SELECT.limit?.rows?.val === 0
71
+ ? Promise.resolve([])
72
+ : executeSelectCQN(model, dbc, query, user, locale, isoTs)
73
+ const countPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
69
74
 
70
- const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
71
- return Promise.all([countResultPromise, resultPromise]).then(([countResults, result]) =>
72
- _arrayWithCount(result, countValue(countResults))
73
- )
75
+ // use allSettled here, so all executions are awaited before we rollback
76
+ return Promise.allSettled([countPromise, resultPromise]).then(results => {
77
+ const rejection = results.find(p => p.status === 'rejected')
78
+ if (rejection) throw rejection.reason
79
+
80
+ const [{ value: countResult }, { value: result }] = results
81
+ return _arrayWithCount(result, countValue(countResult))
82
+ })
74
83
  }
75
84
 
76
85
  return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result =>
@@ -207,7 +207,7 @@ const _redirectRefToActives = (ref, model) => {
207
207
  }
208
208
 
209
209
  const lastCheckMap = new Map()
210
- const _cleanUpOldDrafts = async (service, tenant) => {
210
+ const _cleanUpOldDrafts = (service, tenant) => {
211
211
  if (!DEL_TIMEOUT.value) return
212
212
 
213
213
  const expiryDate = new Date(Date.now() - DEL_TIMEOUT.value).toISOString()
@@ -824,7 +824,7 @@ const Read = {
824
824
  else ownNewDrafts.push(draft)
825
825
  }
826
826
 
827
- const $count = ownDrafts.length + (isCount ? actives[0]?.$count : actives.$count ?? 0)
827
+ const $count = ownDrafts.length + (isCount ? actives[0]?.$count : (actives.$count ?? 0))
828
828
  if (isCount) return { $count }
829
829
 
830
830
  Read.merge(query._target, ownDrafts, [], row => {
@@ -15,7 +15,7 @@ class CustomReferenceBuilder extends ReferenceBuilder {
15
15
  const args = Object.keys(ref[0].args)
16
16
  .map(argKey => {
17
17
  this._outputObj.values.push(ref[0].args[argKey].val)
18
- return `${argKey} => ${this._options.placeholder}`
18
+ return `${this._quoteElement(argKey)} => ${this._options.placeholder}`
19
19
  })
20
20
  .join(', ')
21
21
 
@@ -22,7 +22,7 @@ class EndpointRegistry {
22
22
  // unsuccessful auth doesn't automatically reject!
23
23
  cds.app.use(basePath, (req, res, next) => {
24
24
  // REVISIT: we should probably pass an error into next so that a (custom) error middleware can handle it
25
- if (!cds.context.user._is_anonymous) res.status(401).json({ error: ODATA_UNAUTHORIZED })
25
+ if (cds.context.user._is_anonymous) return res.status(401).json({ error: ODATA_UNAUTHORIZED })
26
26
  next()
27
27
  })
28
28
  } else if (process.env.NODE_ENV === 'production') {
@@ -32,7 +32,7 @@ class EndpointRegistry {
32
32
  cds.app.use(basePath, cds.middlewares.context())
33
33
  }
34
34
  cds.app.use(basePath, express.json({ type: 'application/*+json' }))
35
- cds.app.use(basePath, express.json()) // REVISIT: Do we need both?
35
+ cds.app.use(basePath, express.json())
36
36
  cds.app.use(basePath, express.urlencoded({ extended: true }))
37
37
  LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'OPTIONS' })
38
38
 
@@ -1,5 +1,6 @@
1
1
  const cds = require('../cds')
2
2
 
3
+ const normalizeIncomingMessage = require('./common-utils/normalizeIncomingMessage')
3
4
  const express = require('express')
4
5
  const https = require('https')
5
6
  const crypto = require('crypto')
@@ -47,31 +48,34 @@ function _validateCertificate(req, res, next) {
47
48
  return res.status(401).json({ message: 'Authentication Failed' })
48
49
  }
49
50
 
50
- const bindingCert = new crypto.X509Certificate(this.options.credentials.certificate).toLegacyObject()
51
- const clientCert = new crypto.X509Certificate(
51
+ const clientCertObj = new crypto.X509Certificate(
52
52
  `-----BEGIN CERTIFICATE-----\n${req.headers['x-forwarded-client-cert']}\n-----END CERTIFICATE-----`
53
- ).toLegacyObject()
53
+ )
54
+ const clientCert = clientCertObj.toLegacyObject()
55
+
56
+ if (!this.isMultitenancy && !clientCertObj.checkPrivateKey(this.privateKey))
57
+ return res.status(401).josn({ message: 'Authentication Failed' })
54
58
 
55
59
  const cfSubject = Buffer.from(req.headers['x-ssl-client-subject-cn'], 'base64').toString()
56
- if (bindingCert.subject.CN !== clientCert.subject.CN || bindingCert.subject.CN !== cfSubject) {
60
+ if (this.validationCert.subject.CN !== clientCert.subject.CN || this.validationCert.subject.CN !== cfSubject) {
57
61
  this.LOG.info('certificate subject does not match')
58
62
  return res.status(401).json({ message: 'Authentication Failed' })
59
63
  }
60
64
  this.LOG.debug('incoming Subject CN is valid.')
61
65
 
62
- if (bindingCert.issuer.CN !== clientCert.issuer.CN) {
66
+ if (this.validationCert.issuer.CN !== clientCert.issuer.CN) {
63
67
  this.LOG.info('Certificate issuer subject does not match')
64
68
  return res.status(401).json({ message: 'Authentication Failed' })
65
69
  }
66
70
  this.LOG.debug('incoming issuer subject CN is valid.')
67
71
 
68
- if (bindingCert.issuer.O !== clientCert.issuer.O) {
72
+ if (this.validationCert.issuer.O !== clientCert.issuer.O) {
69
73
  this.LOG.info('Certificate issuer org does not match')
70
74
  return res.status(401).json({ message: 'Authentication Failed' })
71
75
  }
72
76
  this.LOG.debug('incoming Issuer Org is valid.')
73
77
 
74
- if (bindingCert.issuer.OU !== clientCert.issuer.OU) {
78
+ if (this.validationCert.issuer.OU !== clientCert.issuer.OU) {
75
79
  this.LOG.info('certificate issuer OU does not match')
76
80
  return res.status(401).json({ message: 'Authentication Failed' })
77
81
  }
@@ -102,6 +106,11 @@ class EventBroker extends cds.MessagingService {
102
106
  this.startListening()
103
107
  })
104
108
  this.agent = this.getAgent()
109
+ this.isMultitenancy = cds.requires.multitenancy || cds.env.profiles.includes('mtx-sidecar')
110
+ this.validationCert = new crypto.X509Certificate(
111
+ this.isMultitenancy ? this.options.credentials.certificate : this.agent.options.cert
112
+ ).toLegacyObject()
113
+ this.privateKey = !this.isMultitenancy && crypto.createPrivateKey(this.agent.options.key)
105
114
  }
106
115
 
107
116
  getAgent() {
@@ -141,30 +150,63 @@ class EventBroker extends cds.MessagingService {
141
150
 
142
151
  // TODO: What if we're in single tenant variant?
143
152
  try {
144
- const ceSource = `${this.options.credentials.ceSource[0]}/${cds.context.tenant}`
145
153
  const hostname = this.options.credentials.eventing.http.x509.url.replace(/^https?:\/\//, '')
146
- // TODO Cloud Events Handler CAP
154
+
155
+ // take over and cleanse cloudevents headers
156
+ const headers = { ...(msg.headers ?? {}) }
157
+
158
+ const ceId = headers.id
159
+ delete headers.id
160
+
161
+ const ceSource = headers.source
162
+ delete headers.source
163
+
164
+ const ceType = headers.type
165
+ delete headers.type
166
+
167
+ const ceSpecversion = headers.specversion
168
+ delete headers.specversion
169
+
170
+ // const ceDatacontenttype = headers.datacontenttype // not part of the HTTP API
171
+ delete headers.datacontenttype
172
+
173
+ // const ceTime = headers.time // not part of the HTTP API
174
+ delete headers.time
175
+
147
176
  const options = {
148
177
  hostname: hostname,
149
178
  method: 'POST',
150
179
  headers: {
151
- 'ce-id': cds.utils.uuid(),
180
+ 'ce-id': ceId,
152
181
  'ce-source': ceSource,
153
- 'ce-type': msg.event,
154
- 'ce-specversion': '1.0',
155
- 'Content-Type': 'application/json'
182
+ 'ce-type': ceType,
183
+ 'ce-specversion': ceSpecversion,
184
+ 'Content-Type': 'application/json' // because of { data, ...headers } format
156
185
  },
157
186
  agent: this.agent
158
187
  }
159
188
  this.LOG.debug('HTTP headers:', JSON.stringify(options.headers))
160
189
  this.LOG.debug('HTTP body:', JSON.stringify(msg.data))
161
- await request(options, msg.data) // TODO: fetch does not work with mTLS as of today, requires another module. see https://github.com/nodejs/node/issues/48977
190
+ // what about headers?
191
+ // TODO: Clarify if we should send `{ data, ...headers }` vs. `data` + HTTP headers (`ce-*`)
192
+ await request(options, { data: msg.data, ...headers }) // TODO: fetch does not work with mTLS as of today, requires another module. see https://github.com/nodejs/node/issues/48977
162
193
  if (this.LOG._info) this.LOG.info('Emit', { topic: msg.event })
163
194
  } catch (e) {
164
195
  this.LOG.error('Emit failed:', e.message)
165
196
  }
166
197
  }
167
198
 
199
+ prepareHeaders(headers, event) {
200
+ if (!('source' in headers)) {
201
+ if (!this.options.credentials.ceSource)
202
+ throw new Error(
203
+ 'Cannot publish event because of missing source information, currently not part of binding information.'
204
+ )
205
+ headers.source = `${this.options.credentials.ceSource[0]}/${cds.context.tenant}`
206
+ }
207
+ super.prepareHeaders(headers, event)
208
+ }
209
+
168
210
  async registerWebhookEndpoints() {
169
211
  const webhookBasePath = this.options.webhookPath || '/-/cds/event-broker/webhook'
170
212
  cds.app.post(webhookBasePath, _validateCertificate.bind(this))
@@ -176,17 +218,27 @@ class EventBroker extends cds.MessagingService {
176
218
  try {
177
219
  const event = req.headers['ce-type'] // TG27: type contains namespace, so there's no collision
178
220
  const tenant = req.headers['ce-sapconsumertenant']
179
- const msg = {
180
- inbound: true,
181
- event,
182
- tenant,
183
- data: req.body ? req.body : undefined,
184
- headers: req.headers
221
+
222
+ // take over cloudevents headers (`ce-*`) without the prefix
223
+ const headers = {}
224
+ for (const header in req.headers) {
225
+ if (header.startsWith('ce-')) headers[header.slice(3)] = req.headers[header]
185
226
  }
186
- cds.context = { user: cds.User.privileged }
187
- if (tenant) cds.context.tenant = tenant // TODO: In single tenant case, we don't need a tenant
188
- const tx = await this.tx()
189
- await tx.emit(msg)
227
+
228
+ const msg = normalizeIncomingMessage(req.body)
229
+ msg.event = event
230
+ Object.assign(msg.headers, headers)
231
+ if (this.isMultitenancy) msg.tenant = tenant
232
+
233
+ // for cds.context.http
234
+ msg._ = {}
235
+ msg._.req = req
236
+ msg._.res = res
237
+
238
+ const context = { user: cds.User.privileged, _: msg._ }
239
+ if (msg.tenant) context.tenant = msg.tenant
240
+
241
+ await this.tx(context, tx => tx.emit(msg))
190
242
  this.LOG.debug('Event processed successfully.')
191
243
  return res.status(200).json({ message: 'OK' })
192
244
  } catch (e) {
@@ -1,30 +1,3 @@
1
- function getNested(k, obj) {
2
- let cur = obj
3
- let p = ''
4
- const parts = k.split('_')
5
- while (parts.length) {
6
- const q = parts.shift()
7
- if (q in cur) {
8
- cur = cur[q]
9
- p = ''
10
- } else {
11
- p = p ? p + '_' + q : q
12
- if (p in cur) {
13
- cur = cur[p]
14
- p = ''
15
- } else {
16
- if (Object.keys(cur).some(k => k.startsWith(p + '_'))) {
17
- // continue for now as there's still a chance
18
- } else {
19
- // abort
20
- return undefined
21
- }
22
- }
23
- }
24
- }
25
- return cur[p] || cur !== obj ? cur : undefined
26
- }
27
-
28
1
  const getNormalizedDecimal = val => {
29
2
  let v = `${val}`
30
3
  const cgs = v.match(/^(\d*\.*\d*)e([+|-]*)(\d*)$/)
@@ -88,39 +61,10 @@ const resolveCDSType = ele => {
88
61
  return ele
89
62
  }
90
63
 
91
- function resolveSegment(prev, obj, def) {
92
- if (prev.keys) {
93
- let keys = []
94
- for (const k of prev.keys) {
95
- let val
96
- if (k in obj) val = obj[k]
97
- else val = getNested(k, obj)
98
- if (val == null) {
99
- // in some cases, k is not given, e.g., POST into collection via navigation
100
- // TODO: what to put in target? "null", "transient", ...?
101
- if (k === 'IsActiveEntity')
102
- keys.push(`${k}=false`) //> always false if not in obj as it must be a draft activate
103
- else keys.push(`${k}=null`)
104
- } else {
105
- const cdsType = resolveCDSType(def.elements[k])
106
- const odataType = def.elements[k]['@odata.Type']
107
- if (!odataType && cdsType === 'cds.String' || odataType === 'Edm.String') val = `'${val}'`
108
- // TODO: more proper val encoding based on type
109
- keys.push(`${k}=${val}`)
110
- }
111
- }
112
- return `${prev.assoc}(${keys.join(',')})`
113
- }
114
- if (prev.index) {
115
- return `${prev.prop}[${prev.index}]`
116
- }
117
- }
118
64
 
119
65
  module.exports = {
120
- getNested,
121
66
  getNormalizedDecimal,
122
67
  getTarget,
123
68
  isBase64String,
124
- resolveCDSType,
125
- resolveSegment
69
+ resolveCDSType
126
70
  }