@sap/cds 7.6.4 → 7.7.1
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.
- package/CHANGELOG.md +39 -1
- package/_i18n/i18n.properties +3 -0
- package/app/index.js +14 -8
- package/bin/serve.js +51 -19
- package/common.cds +16 -0
- package/lib/auth/ias-auth.js +2 -2
- package/lib/auth/index.js +1 -1
- package/lib/auth/jwt-auth.js +1 -1
- package/lib/compile/cdsc.js +23 -11
- package/lib/compile/for/nodejs.js +2 -2
- package/lib/compile/for/odata.js +4 -0
- package/lib/compile/load.js +7 -2
- package/lib/compile/to/sql.js +3 -0
- package/lib/dbs/cds-deploy.js +197 -220
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +8 -2
- package/lib/linked/types.js +1 -0
- package/lib/log/format/json.js +4 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/SELECT.js +8 -8
- package/lib/req/context.js +22 -13
- package/lib/req/request.js +10 -4
- package/lib/srv/cds-connect.js +9 -3
- package/lib/srv/cds-serve.js +5 -3
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/protocols/odata-v4.js +38 -9
- package/lib/srv/srv-api.js +98 -140
- package/lib/srv/srv-models.js +2 -2
- package/lib/srv/srv-tx.js +1 -0
- package/lib/utils/cds-utils.js +32 -23
- package/lib/utils/data.js +1 -1
- package/lib/utils/tar.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
- package/libx/_runtime/cds-services/util/assert.js +50 -240
- package/libx/_runtime/cds.js +5 -0
- package/libx/_runtime/common/aspects/any.js +53 -45
- package/libx/_runtime/common/generic/input.js +14 -10
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/quotingStyles.js +1 -1
- package/libx/_runtime/common/utils/resolveStructured.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
- package/libx/_runtime/common/utils/stream.js +2 -16
- package/libx/_runtime/common/utils/streamProp.js +16 -6
- package/libx/_runtime/common/utils/ucsn.js +1 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +6 -1
- package/libx/_runtime/fiori/generic/activate.js +11 -3
- package/libx/_runtime/fiori/generic/edit.js +8 -2
- package/libx/_runtime/fiori/lean-draft.js +94 -30
- package/libx/_runtime/hana/execute.js +2 -5
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
- package/libx/_runtime/messaging/service.js +6 -2
- package/libx/common/assert/index.js +232 -0
- package/libx/common/assert/type.js +109 -0
- package/libx/common/assert/utils.js +125 -0
- package/libx/common/assert/validation.js +109 -0
- package/libx/odata/index.js +5 -5
- package/libx/odata/middleware/create.js +83 -0
- package/libx/odata/middleware/delete.js +38 -0
- package/libx/odata/middleware/error.js +8 -0
- package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
- package/libx/odata/middleware/operation.js +78 -0
- package/libx/odata/middleware/parse.js +11 -0
- package/libx/odata/{read.js → middleware/read.js} +42 -20
- package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
- package/libx/odata/middleware/stream.js +237 -0
- package/libx/odata/middleware/update.js +165 -0
- package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
- package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
- package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
- package/libx/odata/{utils.js → utils/index.js} +95 -9
- package/libx/outbox/index.js +2 -1
- package/libx/rest/RestAdapter.js +0 -1
- package/libx/rest/middleware/operation.js +6 -4
- package/libx/rest/middleware/parse.js +20 -2
- package/package.json +1 -1
- package/server.js +43 -71
- package/libx/odata/create.js +0 -44
- package/libx/odata/delete.js +0 -25
- package/libx/odata/error.js +0 -12
- package/libx/odata/update.js +0 -110
- /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
- /package/libx/odata/{parser.js → parse/parser.js} +0 -0
- /package/libx/odata/{result.js → utils/result.js} +0 -0
|
@@ -117,25 +117,11 @@ const enhanceStreamResult = async (req, query, result, model) => {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
const pick = element => {
|
|
120
|
-
return element['@Core.IsURL']
|
|
120
|
+
return element['@Core.IsURL'] || element['@Core.MediaType']
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
const processFn = ({ row, key }) => {
|
|
124
|
-
row[`${key}@odata.mediaReadLink`] = row[key]
|
|
125
124
|
delete row[key]
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
if (!result) return
|
|
130
|
-
if (!Array.isArray(result)) result = [result]
|
|
131
|
-
if (result.length === 0) return
|
|
132
|
-
|
|
133
|
-
const template = getTemplate('redirect-properties', service, req.target, { pick })
|
|
134
|
-
if (template && template.elements.size) {
|
|
135
|
-
for (const row of result) {
|
|
136
|
-
templateProcessor({ processFn, row, template })
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
module.exports = { enhanceStreamResult, transformRedirectProperties }
|
|
127
|
+
module.exports = { enhanceStreamResult }
|
|
@@ -2,7 +2,8 @@ const cds = require('../../cds')
|
|
|
2
2
|
const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/handler')
|
|
3
3
|
const { isDuplicate } = require('./rewriteAsterisks')
|
|
4
4
|
|
|
5
|
-
const _addColumn = (name, type, columns) => {
|
|
5
|
+
const _addColumn = (name, type, columns, url) => {
|
|
6
|
+
if (cds.env.features.odata_new_adapter) return
|
|
6
7
|
if (typeof type === 'object') {
|
|
7
8
|
let mType = type['='].replaceAll(/\./g, '_')
|
|
8
9
|
const ref = {
|
|
@@ -14,13 +15,22 @@ const _addColumn = (name, type, columns) => {
|
|
|
14
15
|
const val = { val: type, as: `${name}@odata.mediaContentType` }
|
|
15
16
|
if (!columns.find(isDuplicate(val))) columns.push(val)
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
if (url) {
|
|
20
|
+
const ref = {
|
|
21
|
+
ref: [name],
|
|
22
|
+
as: `${name}@odata.mediaReadLink`
|
|
23
|
+
}
|
|
24
|
+
if (!columns.find(isDuplicate(ref))) columns.push(ref)
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
const _addColumns = (target, columns) => {
|
|
29
|
+
if (cds.env.features.odata_new_adapter) return
|
|
20
30
|
for (const k in target.elements) {
|
|
21
31
|
const el = target.elements[k]
|
|
22
32
|
if (el['@Core.MediaType']) {
|
|
23
|
-
_addColumn(el.name, el['@Core.MediaType'], columns)
|
|
33
|
+
_addColumn(el.name, el['@Core.MediaType'], columns, el['@Core.IsURL'])
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
}
|
|
@@ -38,11 +48,11 @@ const handleStreamProperties = (target, columns, model) => {
|
|
|
38
48
|
|
|
39
49
|
if (col === '*') {
|
|
40
50
|
_addColumns(target, columns)
|
|
41
|
-
} else if (col.ref && type === 'cds.LargeBinary') {
|
|
51
|
+
} else if (col.ref && (type === 'cds.LargeBinary' || mediaType)) {
|
|
42
52
|
if (mediaType) {
|
|
43
|
-
_addColumn(name, mediaType, columns)
|
|
44
|
-
|
|
45
|
-
if (
|
|
53
|
+
_addColumn(name, mediaType, columns, element['@Core.IsURL'])
|
|
54
|
+
columns.splice(index, 1)
|
|
55
|
+
} else if (!cds.env.features.stream_compat) {
|
|
46
56
|
columns.splice(index, 1)
|
|
47
57
|
}
|
|
48
58
|
} else if (col.expand && col.ref) {
|
|
@@ -100,6 +100,7 @@ const _cleanup = (row, definition, cleanupNull, cleanupStruct, errors, prefix =
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// REVISIT: when needed?
|
|
103
104
|
function convertStructured(service, definition, data, { cleanupNull = false, cleanupStruct = false, errors } = {}) {
|
|
104
105
|
if (!definition) return
|
|
105
106
|
// REVISIT check `structs` mode only for now as uCSN is not yet available
|
|
@@ -803,7 +803,7 @@ class JoinCQNFromExpanded {
|
|
|
803
803
|
return 'DRAFT.DraftAdministrativeData'
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
-
if (isActiveRequired && !defaultLanguage) {
|
|
806
|
+
if (this._SELECT.localized !== false && isActiveRequired && !defaultLanguage) {
|
|
807
807
|
const locale = this._locale ? `${this._locale}.` : ''
|
|
808
808
|
const localized = `localized.${locale}${target}`
|
|
809
809
|
if (this._csn.definitions[localized]) {
|
|
@@ -243,7 +243,7 @@ class InsertBuilder extends BaseBuilder {
|
|
|
243
243
|
if (this.uuidKeys) {
|
|
244
244
|
for (const key of this.uuidKeys) {
|
|
245
245
|
if (!flattenColumnMap.get(key)) {
|
|
246
|
-
columns.push(
|
|
246
|
+
columns.push(this._quoteElement(key))
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
}
|
|
@@ -3,6 +3,10 @@ const resolveStructured = require('../../common/utils/resolveStructured')
|
|
|
3
3
|
|
|
4
4
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
5
5
|
|
|
6
|
+
const _isStreamProperty = element => {
|
|
7
|
+
return element.type === 'cds.LargeBinary' || element['@Core.IsURL']
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
11
|
* This method gets all columns for an entity.
|
|
8
12
|
* It includes the generated foreign keys from managed associations, structured elements and complex and custom types.
|
|
@@ -11,7 +15,7 @@ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
|
11
15
|
* @param entity - the csn entity
|
|
12
16
|
* @returns {Array} - array of columns
|
|
13
17
|
*/
|
|
14
|
-
const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }) => {
|
|
18
|
+
const getColumns = (entity, { _4db, onlyKeys, omitStream } = { _4db: true, onlyKeys: false, omitStream: false }) => {
|
|
15
19
|
// REVISIT is this correct or just a problem that occurs because of new structure we do not deal with yet?
|
|
16
20
|
if (!(entity && entity.elements)) return []
|
|
17
21
|
const columnNames = []
|
|
@@ -23,6 +27,7 @@ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }
|
|
|
23
27
|
const element = elements[elementName]
|
|
24
28
|
if (element['@cds.api.ignore']) continue
|
|
25
29
|
if (onlyKeys && !element.key) continue
|
|
30
|
+
if (omitStream && _isStreamProperty(element)) continue
|
|
26
31
|
if (element.isAssociation) continue
|
|
27
32
|
if (!lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
|
|
28
33
|
if (structs && element.elements) {
|
|
@@ -171,9 +171,17 @@ const fioriGenericActivate = async function (req, next) {
|
|
|
171
171
|
})
|
|
172
172
|
])
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
if (event === 'CREATE') {
|
|
175
|
+
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
176
|
+
// status code must be set in handler to allow overriding for FE V2
|
|
177
|
+
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
178
|
+
if (req._?.odataRes) {
|
|
179
|
+
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
180
|
+
} else if (req.http?.res) {
|
|
181
|
+
req.http.res.status(201)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
return result
|
|
178
186
|
}
|
|
179
187
|
|
|
@@ -141,8 +141,14 @@ const fioriGenericEdit = async function (req, next) {
|
|
|
141
141
|
await Promise.all(insertCQNs.map(CQN => dbtx.run(CQN)))
|
|
142
142
|
|
|
143
143
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
144
|
-
//
|
|
145
|
-
|
|
144
|
+
// status code must be set in handler to allow overriding for FE V2
|
|
145
|
+
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
146
|
+
if (req._?.odataRes) {
|
|
147
|
+
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
148
|
+
} else if (req.http?.res) {
|
|
149
|
+
req.http.res.status(201)
|
|
150
|
+
}
|
|
151
|
+
|
|
146
152
|
return results[0][0]
|
|
147
153
|
}
|
|
148
154
|
|
|
@@ -8,31 +8,71 @@ const original = Symbol('original')
|
|
|
8
8
|
const DRAFT_PARAMS = Symbol('draftParams')
|
|
9
9
|
const AGGREGATION_FUNCTIONS = ['sum', 'min', 'max', 'avg', 'count']
|
|
10
10
|
|
|
11
|
+
const calcTimeMs = timeout => {
|
|
12
|
+
const match = timeout.match(/^([0-9]+)(w|d|h|hrs|min)$/)
|
|
13
|
+
if (!match) return
|
|
14
|
+
const [, val, t] = match
|
|
15
|
+
switch (t) {
|
|
16
|
+
case 'w':
|
|
17
|
+
return val * 1000 * 3600 * 24 * 7
|
|
18
|
+
case 'd':
|
|
19
|
+
return val * 1000 * 3600 * 24
|
|
20
|
+
case 'h':
|
|
21
|
+
case 'hrs':
|
|
22
|
+
return val * 1000 * 3600
|
|
23
|
+
case 'min':
|
|
24
|
+
return val * 1000 * 60
|
|
25
|
+
default:
|
|
26
|
+
return val
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const _config_to_ms = (config, _default) => {
|
|
31
|
+
const timeout = cds.env.fiori?.[config]
|
|
32
|
+
let timeout_ms
|
|
33
|
+
if (timeout === true) {
|
|
34
|
+
timeout_ms = calcTimeMs(_default)
|
|
35
|
+
} else if (typeof timeout === 'string') {
|
|
36
|
+
timeout_ms = calcTimeMs(timeout)
|
|
37
|
+
if (!timeout_ms)
|
|
38
|
+
throw new Error(`
|
|
39
|
+
${timeout} is an invalid value for \`cds.fiori.${config}\`.
|
|
40
|
+
Please provide a value in format /^([0-9]+)(w|d|h|hrs|min)$/.
|
|
41
|
+
`)
|
|
42
|
+
} else {
|
|
43
|
+
timeout_ms = timeout
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return timeout_ms
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
const DEL_TIMEOUT = {
|
|
12
50
|
get value() {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
51
|
+
const timeout_ms = _config_to_ms('draft_deletion_timeout', '30d')
|
|
52
|
+
Object.defineProperty(DEL_TIMEOUT, 'value', { value: timeout_ms })
|
|
53
|
+
return timeout_ms
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const LOCK_TIMEOUT = {
|
|
58
|
+
get value() {
|
|
59
|
+
let timeout_ms = _config_to_ms('draft_lock_timeout', '15min')
|
|
60
|
+
|
|
61
|
+
const deprecated = cds.env.drafts?.cancellationTimeout // in min
|
|
62
|
+
if (deprecated) {
|
|
63
|
+
// in order to still support legacy use cases for tests, e. g. 0.000001
|
|
64
|
+
timeout_ms = deprecated * 1000 * 60
|
|
19
65
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? Number(parts[1]) * 1000 * 3600 * 24
|
|
24
|
-
: Number(parts[1]) * 1000 * 3600
|
|
25
|
-
: Number(timeout) || 0
|
|
26
|
-
|
|
27
|
-
Object.defineProperty(DEL_TIMEOUT, 'value', { value: result })
|
|
28
|
-
return result
|
|
66
|
+
|
|
67
|
+
Object.defineProperty(LOCK_TIMEOUT, 'value', { value: timeout_ms })
|
|
68
|
+
return timeout_ms
|
|
29
69
|
}
|
|
30
70
|
}
|
|
31
71
|
|
|
32
72
|
const reject_bypassed_draft = req => {
|
|
33
73
|
const msg =
|
|
34
74
|
!cds.profiles?.includes('production') &&
|
|
35
|
-
'`cds.env.fiori.bypass_draft` must be enabled to support the directly modification of active instances.'
|
|
75
|
+
'`cds.env.fiori.bypass_draft` must be enabled or the entity must be annotated with `@odata.draft.bypass` to support the directly modification of active instances.'
|
|
36
76
|
return req.reject(501, msg)
|
|
37
77
|
}
|
|
38
78
|
|
|
@@ -116,13 +156,10 @@ const _inProcessByUserXpr = lockShiftedNow => ({
|
|
|
116
156
|
|
|
117
157
|
const _lock = {
|
|
118
158
|
get shiftedNow() {
|
|
119
|
-
return new Date(Math.max(0, Date.now() -
|
|
159
|
+
return new Date(Math.max(0, Date.now() - LOCK_TIMEOUT.value)).toISOString()
|
|
120
160
|
}
|
|
121
161
|
}
|
|
122
162
|
|
|
123
|
-
const DRAFT_CANCEL_TIMEOUT_IN_MIN = () =>
|
|
124
|
-
(cds.env.drafts?.cancellationTimeout && Number(cds.env.drafts?.cancellationTimeout)) || 15
|
|
125
|
-
|
|
126
163
|
const _redirectRefToDrafts = (ref, model) => {
|
|
127
164
|
const [root, ...tail] = ref
|
|
128
165
|
const draft = model.definitions[root.id || root].drafts
|
|
@@ -281,7 +318,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
281
318
|
if (req.event === 'NEW' || req.event === 'CANCEL' || req.event === 'draftPrepare') {
|
|
282
319
|
if (req.event === 'draftPrepare' && draftParams.IsActiveEntity) req.reject(400)
|
|
283
320
|
if (req.event === 'NEW' && req.data?.IsActiveEntity === true) {
|
|
284
|
-
if (!cds.env.fiori.bypass_draft) return reject_bypassed_draft(req)
|
|
321
|
+
if (!cds.env.fiori.bypass_draft && !req.target['@odata.draft.bypass']) return reject_bypassed_draft(req)
|
|
285
322
|
const containsDraftRoot =
|
|
286
323
|
this.model.definitions[query.INSERT.into?.ref?.[0]?.id || query.INSERT.into?.ref?.[0] || query.INSERT.into][
|
|
287
324
|
'@Common.DraftRoot.ActivationAction'
|
|
@@ -390,6 +427,19 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
390
427
|
const HasActiveEntity = res.HasActiveEntity
|
|
391
428
|
delete res.HasActiveEntity
|
|
392
429
|
|
|
430
|
+
if (cds.env.features.cds_assert) {
|
|
431
|
+
const assertOptions = { path: [req.target.actions[req.event]['@cds.odata.bindingparameter.name'] || 'in'] }
|
|
432
|
+
const errs = cds.assert(res, req.target, assertOptions)
|
|
433
|
+
if (errs) {
|
|
434
|
+
if (errs.length === 1) throw Object.assign(errs[0], { '@Common.numericSeverity': 4 })
|
|
435
|
+
throw Object.assign(new Error('MULTIPLE_ERRORS'), {
|
|
436
|
+
statusCode: 400,
|
|
437
|
+
details: errs,
|
|
438
|
+
'@Common.numericSeverity': 4 //> TODO: should not be needed here
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
393
443
|
// First run the handlers as they might need access to DraftAdministrativeData or the draft entities
|
|
394
444
|
const result = await run(
|
|
395
445
|
HasActiveEntity
|
|
@@ -402,12 +452,18 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
402
452
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: DraftAdministrativeData_DraftUUID })
|
|
403
453
|
])
|
|
404
454
|
|
|
405
|
-
if (!HasActiveEntity)
|
|
455
|
+
if (!HasActiveEntity) {
|
|
456
|
+
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
457
|
+
// status code must be set in handler to allow overriding for FE V2
|
|
458
|
+
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
459
|
+
if (req._?.odataRes) {
|
|
460
|
+
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
461
|
+
} else if (req.http?.res) {
|
|
462
|
+
req.http.res.status(201)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
406
465
|
|
|
407
466
|
return Object.assign(result, { IsActiveEntity: true })
|
|
408
|
-
|
|
409
|
-
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
410
|
-
// status code must be set in handler to allow overriding for FE V2
|
|
411
467
|
}
|
|
412
468
|
|
|
413
469
|
if (req.target.actions?.[req.event] && draftParams.IsActiveEntity === false) {
|
|
@@ -457,7 +513,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
457
513
|
|
|
458
514
|
LOG.debug('patch active')
|
|
459
515
|
|
|
460
|
-
if (!cds.env.fiori.bypass_draft) return reject_bypassed_draft(req)
|
|
516
|
+
if (!cds.env.fiori.bypass_draft && !req.target['@odata.draft.bypass']) return reject_bypassed_draft(req)
|
|
461
517
|
|
|
462
518
|
const entityRef = query.UPDATE.entity.ref
|
|
463
519
|
|
|
@@ -1056,7 +1112,10 @@ function _cleansed(query, model) {
|
|
|
1056
1112
|
}
|
|
1057
1113
|
if (ignoredElements.has(e) && xpr[i + 2]) {
|
|
1058
1114
|
let { val } = xpr[i + 2]
|
|
1059
|
-
|
|
1115
|
+
const param = x.ref.join('_')
|
|
1116
|
+
// outer-most parameters win
|
|
1117
|
+
if (draftParams[param] === undefined)
|
|
1118
|
+
draftParams[param] = xpr[i + 1] === '!=' ? (typeof val === 'boolean' ? !val : 'not ' + val) : val
|
|
1060
1119
|
i += 2
|
|
1061
1120
|
const last = cleansed[cleansed.length - 1]
|
|
1062
1121
|
if (last === 'and' || last === 'or') cleansed.pop()
|
|
@@ -1298,7 +1357,7 @@ async function onEdit(req) {
|
|
|
1298
1357
|
}
|
|
1299
1358
|
|
|
1300
1359
|
if (!res) req.reject(404)
|
|
1301
|
-
const preserveChanges = req.
|
|
1360
|
+
const preserveChanges = req.data?.PreserveChanges
|
|
1302
1361
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
1303
1362
|
|
|
1304
1363
|
if (draft) {
|
|
@@ -1332,8 +1391,13 @@ async function onEdit(req) {
|
|
|
1332
1391
|
await INSERT.into(targetDraft).entries(res)
|
|
1333
1392
|
|
|
1334
1393
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
1335
|
-
//
|
|
1336
|
-
|
|
1394
|
+
// status code must be set in handler to allow overriding for FE V2
|
|
1395
|
+
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
1396
|
+
if (req._?.odataRes) {
|
|
1397
|
+
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
1398
|
+
} else if (req.http?.res) {
|
|
1399
|
+
req.http.res.status(201)
|
|
1400
|
+
}
|
|
1337
1401
|
|
|
1338
1402
|
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
1339
1403
|
}
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
readStreamWithHdb
|
|
16
16
|
} = require('./streaming')
|
|
17
17
|
const { convertStream } = require('../db/utils/stream')
|
|
18
|
+
const { isBase64String } = require('../../common/assert/utils')
|
|
18
19
|
|
|
19
20
|
function _cqnToSQL(model, query, user, locale, txTimestamp) {
|
|
20
21
|
return sqlFactory(
|
|
@@ -48,8 +49,6 @@ function _getBinaries(stmt) {
|
|
|
48
49
|
}, [])
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}={2})$/
|
|
52
|
-
|
|
53
52
|
function _getProcedureNameAndSchema(sql) {
|
|
54
53
|
// name delimited with "" allows any character
|
|
55
54
|
const match = sql
|
|
@@ -142,9 +141,7 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
142
141
|
const vals = Array.isArray(values[0]) ? values : [values]
|
|
143
142
|
for (const i of binaries) {
|
|
144
143
|
for (const row of vals) {
|
|
145
|
-
if (row[i] &&
|
|
146
|
-
row[i] = Buffer.from(row[i], 'base64')
|
|
147
|
-
}
|
|
144
|
+
if (row[i] && isBase64String(row[i])) row[i] = Buffer.from(row[i], 'base64')
|
|
148
145
|
}
|
|
149
146
|
}
|
|
150
147
|
}
|
|
@@ -17,15 +17,15 @@ class EndpointRegistry {
|
|
|
17
17
|
if (isSecured()) {
|
|
18
18
|
if (cds.requires.auth.impl) {
|
|
19
19
|
if (cds.env.requires.middlewares !== false) {
|
|
20
|
-
|
|
20
|
+
cds.app.use(basePath, cds.middlewares.before) // contains auth, trace, context
|
|
21
21
|
} else {
|
|
22
22
|
const impl = _require(cds.resolve(cds.requires.auth.impl))
|
|
23
|
-
|
|
23
|
+
cds.app.use(basePath, impl)
|
|
24
24
|
}
|
|
25
25
|
} else {
|
|
26
26
|
if (cds.env.requires.middlewares !== false) {
|
|
27
27
|
const jwt_auth = require('../../../../lib/auth/jwt-auth.js')
|
|
28
|
-
|
|
28
|
+
cds.app.use(basePath, jwt_auth(cds.requires.auth))
|
|
29
29
|
} else {
|
|
30
30
|
const JWTStrategy = require('../../auth/strategies/JWT.js')
|
|
31
31
|
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
@@ -33,28 +33,22 @@ class EndpointRegistry {
|
|
|
33
33
|
// REVISIT: It's unclear if the credentials from cds.requires.auth need to be used here.
|
|
34
34
|
// In principle, user-facing endpoints might differ from messaging ones.
|
|
35
35
|
passport.use(new JWTStrategy(cds.requires.auth.credentials))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cds.app.use(path, passport.authenticate('JWT', { session: false }))
|
|
39
|
-
})
|
|
36
|
+
cds.app.use(basePath, passport.initialize())
|
|
37
|
+
cds.app.use(basePath, passport.authenticate('JWT', { session: false }))
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
// unsuccessful auth doesn't automatically reject!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
next()
|
|
48
|
-
})
|
|
41
|
+
cds.app.use(basePath, (req, res, next) => {
|
|
42
|
+
// REVISIT: we should probably pass an error into next so that a (custom) error middleware can handle it
|
|
43
|
+
if (!req.user) res.status(401).json({ error: ODATA_UNAUTHORIZED })
|
|
44
|
+
next()
|
|
49
45
|
})
|
|
50
46
|
} else if (process.env.NODE_ENV === 'production') {
|
|
51
47
|
LOG.warn('Messaging endpoints not secured')
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
cds.app.use(path, express.urlencoded({ extended: true }))
|
|
57
|
-
})
|
|
49
|
+
cds.app.use(basePath, express.json({ type: 'application/*+json' }))
|
|
50
|
+
cds.app.use(basePath, express.json())
|
|
51
|
+
cds.app.use(basePath, express.urlencoded({ extended: true }))
|
|
58
52
|
LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'OPTIONS' })
|
|
59
53
|
|
|
60
54
|
// Clear cds.context as it would interfere with subsequent transactions
|
|
@@ -62,10 +56,6 @@ class EndpointRegistry {
|
|
|
62
56
|
cds.context = undefined
|
|
63
57
|
next()
|
|
64
58
|
})
|
|
65
|
-
cds.app.use(deployPath, (_req, _res, next) => {
|
|
66
|
-
cds.context = undefined
|
|
67
|
-
next()
|
|
68
|
-
})
|
|
69
59
|
|
|
70
60
|
cds.app.options(basePath, (req, res) => {
|
|
71
61
|
try {
|
|
@@ -126,8 +126,12 @@ class MessagingService extends cds.Service {
|
|
|
126
126
|
const subscribedEvent =
|
|
127
127
|
this.subscribedTopics.get(_msg.event) ||
|
|
128
128
|
(this.wildcarded && this.subscribedTopics.get(this.wildcarded(_msg.event)))
|
|
129
|
-
if (!subscribedEvent && !this._listenToAll.value)
|
|
130
|
-
|
|
129
|
+
if (!subscribedEvent && !this._listenToAll.value) {
|
|
130
|
+
const err = new Error(`No handler for incoming message with topic '${_msg.event}' found.`)
|
|
131
|
+
err.code = 'NO_HANDLER_FOUND' // consumers might want to react to that
|
|
132
|
+
throw err
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
_msg.event = subscribedEvent || _msg.event
|
|
132
136
|
}
|
|
133
137
|
return _msg
|