@sap/cds 6.8.3 → 7.0.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.
- package/CHANGELOG.md +61 -2
- package/README.md +0 -1
- package/bin/cds-serve.js +50 -3
- package/bin/deploy/to-hana.js +1 -0
- package/bin/serve.js +16 -20
- package/lib/auth/basic-auth.js +6 -4
- package/lib/auth/index.js +4 -3
- package/lib/auth/jwt-auth.js +2 -5
- package/lib/compile/cds-compile.js +34 -89
- package/lib/compile/cdsc.js +11 -0
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/lean_drafts.js +36 -69
- package/lib/compile/for/nodejs.js +2 -1
- package/lib/compile/load.js +1 -1
- package/lib/compile/minify.js +2 -0
- package/lib/compile/to/csn.js +74 -0
- package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +8 -6
- package/lib/dbs/cds-deploy.js +174 -114
- package/lib/env/cds-env.js +64 -79
- package/lib/env/cds-requires.js +11 -28
- package/lib/env/defaults.js +13 -3
- package/lib/env/plugins.js +1 -12
- package/lib/env/presets.js +25 -21
- package/lib/index.js +121 -147
- package/lib/{core/reflect.js → linked/models.js} +2 -2
- package/lib/{core/infer.js → linked/queries.js} +2 -0
- package/lib/{core/index.js → linked/types.js} +2 -1
- package/lib/log/cds-error.js +13 -7
- package/lib/log/format/cf.js +1 -1
- package/lib/plugins.js +49 -0
- package/lib/ql/Query.js +0 -9
- package/lib/ql/STREAM.js +0 -1
- package/lib/req/context.js +2 -7
- package/lib/req/request.js +6 -2
- package/lib/req/response.js +23 -10
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/middlewares/errors.js +1 -1
- package/lib/srv/protocols/_legacy.js +1 -0
- package/lib/srv/protocols/graphql.js +7 -16
- package/lib/srv/protocols/index.js +59 -45
- package/lib/srv/protocols/odata-v2-proxy.js +2 -70
- package/lib/srv/srv-api.js +9 -3
- package/lib/srv/srv-dispatch.js +12 -9
- package/lib/srv/srv-models.js +4 -21
- package/lib/srv/srv-tx.js +15 -12
- package/lib/utils/cds-test.js +14 -9
- package/lib/utils/cds-utils.js +2 -12
- package/lib/utils/check-version.js +17 -0
- package/{bin/build → lib/utils}/csv-reader.js +23 -24
- package/libx/_runtime/auth/index.js +27 -23
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
- package/libx/_runtime/cds-services/services/Service.js +79 -107
- package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
- package/libx/_runtime/cds-services/util/assert.js +65 -2
- package/libx/_runtime/common/composition/data.js +1 -0
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/restrict.js +5 -10
- package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
- package/libx/_runtime/common/generic/auth/utils.js +1 -2
- package/libx/_runtime/common/generic/crud.js +32 -16
- package/libx/_runtime/common/generic/etag.js +133 -104
- package/libx/_runtime/common/generic/input.js +6 -21
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/stream.js +52 -0
- package/libx/_runtime/common/generic/temporal.js +25 -8
- package/libx/_runtime/common/i18n/messages.properties +0 -2
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/common/utils/csn.js +0 -51
- package/libx/_runtime/common/utils/etag.js +30 -0
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
- package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
- package/libx/_runtime/common/utils/stream.js +140 -0
- package/libx/_runtime/common/utils/streamProp.js +29 -12
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
- package/libx/_runtime/db/generic/index.js +0 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +2 -2
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/query/run.js +2 -2
- package/libx/_runtime/db/query/update.js +2 -2
- package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
- package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
- package/libx/_runtime/fiori/draft.js +2 -0
- package/libx/_runtime/fiori/generic/activate.js +8 -9
- package/libx/_runtime/fiori/generic/before.js +30 -20
- package/libx/_runtime/fiori/generic/cancel.js +5 -3
- package/libx/_runtime/fiori/generic/delete.js +5 -3
- package/libx/_runtime/fiori/generic/edit.js +7 -7
- package/libx/_runtime/fiori/generic/index.js +10 -16
- package/libx/_runtime/fiori/generic/new.js +5 -3
- package/libx/_runtime/fiori/generic/patch.js +11 -8
- package/libx/_runtime/fiori/generic/prepare.js +13 -6
- package/libx/_runtime/fiori/generic/read.js +12 -6
- package/libx/_runtime/fiori/lean-draft.js +207 -152
- package/libx/_runtime/fiori/utils/delete.js +10 -5
- package/libx/_runtime/fiori/utils/req.js +17 -5
- package/libx/_runtime/fiori/utils/stream.js +36 -0
- package/libx/_runtime/hana/Service.js +12 -9
- package/libx/_runtime/hana/conversion.js +10 -15
- package/libx/_runtime/hana/driver.js +2 -0
- package/libx/_runtime/hana/execute.js +28 -6
- package/libx/_runtime/hana/pool.js +36 -122
- package/libx/_runtime/hana/search2cqn4sql.js +34 -36
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/remote/Service.js +20 -1
- package/libx/_runtime/remote/utils/client.js +3 -5
- package/libx/_runtime/sqlite/Service.js +4 -6
- package/libx/_runtime/sqlite/conversion.js +3 -13
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
- package/libx/_runtime/sqlite/execute.js +5 -16
- package/libx/odata/afterburner.js +22 -6
- package/libx/odata/grammar.pegjs +6 -1
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +16 -9
- package/libx/rest/RestRequest.js +1 -1
- package/libx/rest/middleware/input.js +2 -1
- package/libx/rest/middleware/operation.js +1 -0
- package/libx/rest/middleware/parse.js +3 -2
- package/libx/rest/middleware/payload.js +9 -8
- package/libx/rest/middleware/read.js +1 -0
- package/package.json +9 -16
- package/app/fiori/preview.js +0 -270
- package/app/fiori/routes.js +0 -59
- package/bin/build/buildTaskEngine.js +0 -360
- package/bin/build/buildTaskFactory.js +0 -283
- package/bin/build/buildTaskHandler.js +0 -241
- package/bin/build/buildTaskProvider.js +0 -22
- package/bin/build/buildTaskProviderFactory.js +0 -175
- package/bin/build/cds.js +0 -5
- package/bin/build/constants.js +0 -66
- package/bin/build/index.js +0 -58
- package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
- package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
- package/bin/build/provider/buildTaskProviderInternal.js +0 -383
- package/bin/build/provider/fiori/index.js +0 -171
- package/bin/build/provider/hana/2migration.js +0 -179
- package/bin/build/provider/hana/index.js +0 -505
- package/bin/build/provider/hana/migrationtable.js +0 -472
- package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
- package/bin/build/provider/hana/template/.hdinamespace +0 -4
- package/bin/build/provider/hana/template/package.json +0 -12
- package/bin/build/provider/hana/template/undeploy.json +0 -5
- package/bin/build/provider/java/index.js +0 -111
- package/bin/build/provider/java-cf/index.js +0 -1
- package/bin/build/provider/mtx/index.js +0 -268
- package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
- package/bin/build/provider/mtx-extension/index.js +0 -131
- package/bin/build/provider/mtx-sidecar/index.js +0 -137
- package/bin/build/provider/node-cf/index.js +0 -1
- package/bin/build/provider/nodejs/index.js +0 -192
- package/bin/build/util.js +0 -299
- package/bin/cds.js +0 -125
- package/bin/deploy/to-hana/cfUtil.js +0 -355
- package/bin/deploy/to-hana/gitUtil.js +0 -57
- package/bin/deploy/to-hana/hana.js +0 -306
- package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
- package/bin/deploy/to-hana/index.js +0 -16
- package/bin/deploy/to-hana/mtaUtil.js +0 -170
- package/bin/mtx/in-cds.js +0 -17
- package/bin/plugins.js +0 -32
- package/bin/run.js +0 -24
- package/bin/utils/log.js +0 -24
- package/bin/version.js +0 -178
- package/libx/_runtime/audit/Service.js +0 -222
- package/libx/_runtime/audit/generic/personal/access.js +0 -61
- package/libx/_runtime/audit/generic/personal/index.js +0 -56
- package/libx/_runtime/audit/generic/personal/modification.js +0 -132
- package/libx/_runtime/audit/generic/personal/utils.js +0 -186
- package/libx/_runtime/audit/utils/log.js +0 -23
- package/libx/_runtime/audit/utils/v2.js +0 -176
- package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
- package/libx/_runtime/db/generic/integrity.js +0 -455
- package/srv/audit-log.cds +0 -87
- package/srv/mtx.cds +0 -2
- package/srv/mtx.js +0 -8
- /package/lib/{core → linked}/classes.js +0 -0
- /package/lib/{core → linked}/entities.js +0 -0
|
@@ -2,6 +2,7 @@ const cds = require('../cds'),
|
|
|
2
2
|
{ Object_keys } = cds.utils
|
|
3
3
|
const LOG = cds.log('fiori|drafts')
|
|
4
4
|
const original = Symbol('original')
|
|
5
|
+
const DRAFT_PARAMS = Symbol('draftParams')
|
|
5
6
|
|
|
6
7
|
const DRAFT_ELEMENTS = new Set([
|
|
7
8
|
'IsActiveEntity',
|
|
@@ -32,23 +33,11 @@ const _fillIsActiveEntity = (row, IsActiveEntity, target) => {
|
|
|
32
33
|
if (!prop) continue
|
|
33
34
|
const el = target.elements[key]
|
|
34
35
|
const childIsActiveEntity = el._target.isDraft ? IsActiveEntity : true
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const propArray = Array.isArray(prop) ? prop : [prop]
|
|
37
|
+
propArray.forEach(r => _fillIsActiveEntity(r, childIsActiveEntity, el._target))
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// REVISIT: should not be necessary
|
|
41
|
-
const _runWithContext = (srv, req, obj) => {
|
|
42
|
-
const r = new cds.Request(obj)
|
|
43
|
-
r.event // invoke getter
|
|
44
|
-
r._ = Object.assign(r._, req._)
|
|
45
|
-
if (req.getUriInfo) r.getUriInfo = () => req.getUriInfo()
|
|
46
|
-
if (req.getUrlObject) r.getUrlObject = () => req.getUrlObject()
|
|
47
|
-
r._.params = req.params
|
|
48
|
-
r._.query = req.query
|
|
49
|
-
return srv.dispatch(r)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
41
|
/// It's important to wait for the completion of all promises, otherwise a rollback might happen too soon
|
|
53
42
|
const _promiseAll = async array => {
|
|
54
43
|
const results = await Promise.allSettled(array)
|
|
@@ -105,25 +94,40 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
105
94
|
if (
|
|
106
95
|
!req.query ||
|
|
107
96
|
req.query.UPSERT || // skip UPSERTs (might have an additional INSERT)
|
|
108
|
-
(!req.query.SELECT && !req.query.INSERT && !req.query.UPDATE && !req.query.DELETE) ||
|
|
109
|
-
req.query
|
|
110
|
-
)
|
|
97
|
+
(!req.query.SELECT && !req.query.INSERT && !req.query.UPDATE && !req.query.DELETE && !req.query.STREAM) ||
|
|
98
|
+
req.query[DRAFT_PARAMS]
|
|
99
|
+
) {
|
|
111
100
|
return handle(req)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const _etagValidationType = req.headers['if-match']
|
|
104
|
+
? 'if-match'
|
|
105
|
+
: req.headers['if-none-match']
|
|
106
|
+
? 'if-none-match'
|
|
107
|
+
: undefined
|
|
108
|
+
|
|
112
109
|
const query = _cleansed(req.query, this.model)
|
|
113
110
|
_cleanseParams(req.params, req.target)
|
|
114
111
|
if (req.data) _cleanseParams(req.data, req.target)
|
|
115
|
-
const draftParams = query
|
|
112
|
+
const draftParams = query[DRAFT_PARAMS]
|
|
116
113
|
|
|
117
|
-
const _newReq = (req, query, draftParams, event) => {
|
|
114
|
+
const _newReq = (req, query, draftParams, { event, headers }) => {
|
|
118
115
|
// REVISIT: This is a bit hacky -> better way?
|
|
119
116
|
query._target = undefined
|
|
120
|
-
query
|
|
117
|
+
query[DRAFT_PARAMS] = draftParams
|
|
121
118
|
cds.infer(query, this.model.definitions)
|
|
122
119
|
|
|
123
120
|
// REVISIT: This is extremely bad. We should be able to just create a copy without such hacks.
|
|
124
121
|
const _req = cds.Request.for(req._) // REVISIT: this causes req._.data of WRITE reqs copied to READ reqs
|
|
122
|
+
|
|
123
|
+
if (headers) {
|
|
124
|
+
_req.headers = Object.create(req.headers)
|
|
125
|
+
Object.assign(_req.headers, headers)
|
|
126
|
+
}
|
|
127
|
+
|
|
125
128
|
// If we create a `READ` event based on a modifying request, we delete data
|
|
126
129
|
if (event === 'READ' && req.event !== 'READ') delete _req.data // which we fix here -> but this is an ugly workaround
|
|
130
|
+
|
|
127
131
|
_req.query = query
|
|
128
132
|
_req.event =
|
|
129
133
|
event ||
|
|
@@ -137,10 +141,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
137
141
|
_req._ = Object.assign({}, req._ || {})
|
|
138
142
|
_req._.params = req.params
|
|
139
143
|
_req._.query = query
|
|
140
|
-
|
|
141
|
-
props.forEach(p => {
|
|
142
|
-
if (req[p]) _req.p = req[p]
|
|
143
|
-
})
|
|
144
|
+
if (req.protocol) _req.protocol = req.protocol
|
|
144
145
|
_req._ = req._
|
|
145
146
|
const cqnData = _req.query.UPDATE?.data || _req.query.INSERT?.entries?.[0]
|
|
146
147
|
if (cqnData) _req.data = cqnData // must point to the same object
|
|
@@ -150,14 +151,48 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
150
151
|
}
|
|
151
152
|
})
|
|
152
153
|
if (req.tx) _req.tx = req.tx
|
|
154
|
+
|
|
153
155
|
return _req
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
const run =
|
|
157
|
-
const _req = _newReq(req, query, draftParams)
|
|
158
|
+
const run = (query, options = {}) => {
|
|
159
|
+
const _req = _newReq(req, query, draftParams, options)
|
|
158
160
|
return handle(_req)
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
if (req.event === 'READ') {
|
|
164
|
+
if (
|
|
165
|
+
!Object.keys(draftParams).length &&
|
|
166
|
+
!req.query._target.name?.endsWith('DraftAdministrativeData') &&
|
|
167
|
+
!req.query._target.drafts
|
|
168
|
+
) {
|
|
169
|
+
req.query = query
|
|
170
|
+
return handle(req)
|
|
171
|
+
}
|
|
172
|
+
const read = req.query._target.name.endsWith('.drafts')
|
|
173
|
+
? Read.ownDrafts
|
|
174
|
+
: draftParams.IsActiveEntity === false && draftParams.SiblingEntity_IsActiveEntity === null
|
|
175
|
+
? Read.all
|
|
176
|
+
: draftParams.IsActiveEntity === true &&
|
|
177
|
+
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
178
|
+
(draftParams.DraftAdministrativeData_InProcessByUser === 'not null' ||
|
|
179
|
+
draftParams.DraftAdministrativeData_InProcessByUser === 'not ')
|
|
180
|
+
? Read.lockedByAnotherUser
|
|
181
|
+
: draftParams.IsActiveEntity === true &&
|
|
182
|
+
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
183
|
+
draftParams.DraftAdministrativeData_InProcessByUser === ''
|
|
184
|
+
? Read.unsavedChangesByAnotherUser
|
|
185
|
+
: draftParams.IsActiveEntity === true && draftParams.HasDraftEntity === false
|
|
186
|
+
? Read.unchanged
|
|
187
|
+
: draftParams.IsActiveEntity === true
|
|
188
|
+
? Read.onlyActives
|
|
189
|
+
: draftParams.IsActiveEntity === false
|
|
190
|
+
? Read.ownDrafts
|
|
191
|
+
: Read.onlyActives
|
|
192
|
+
const result = await read(run, query)
|
|
193
|
+
return result
|
|
194
|
+
}
|
|
195
|
+
|
|
161
196
|
if (req.event === 'NEW' || req.event === 'CANCEL' || req.event === 'draftPrepare') {
|
|
162
197
|
if (draftParams.IsActiveEntity) req.reject(501)
|
|
163
198
|
req.target = req.target.drafts
|
|
@@ -167,34 +202,29 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
167
202
|
else if (query.INSERT.into.ref) query.INSERT.into.ref = _redirectRefToDrafts(query.INSERT.into.ref, this.model)
|
|
168
203
|
} else if (query.DELETE?.from?.ref) query.DELETE.from.ref = _redirectRefToDrafts(query.DELETE.from.ref, this.model)
|
|
169
204
|
else if (query.SELECT?.from?.ref) query.SELECT.from.ref = _redirectRefToDrafts(query.SELECT.from.ref, this.model)
|
|
170
|
-
const _req = _newReq(req, query, draftParams, req.event)
|
|
205
|
+
const _req = _newReq(req, query, draftParams, { event: req.event })
|
|
171
206
|
const result = await handle(_req)
|
|
172
207
|
return result
|
|
173
208
|
}
|
|
174
209
|
|
|
175
210
|
if (req.event === 'DELETE' && draftParams.IsActiveEntity) {
|
|
176
211
|
const draftsRef = _redirectRefToDrafts(query.DELETE.from.ref, this.model)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
])
|
|
186
|
-
)
|
|
212
|
+
const draft = await SELECT.one.from({ ref: draftsRef }).columns([
|
|
213
|
+
{ ref: ['DraftAdministrativeData_DraftUUID'] },
|
|
214
|
+
{
|
|
215
|
+
ref: ['DraftAdministrativeData'],
|
|
216
|
+
expand: [_inProcessByUserXpr(_lock.shiftedNow)]
|
|
217
|
+
}
|
|
218
|
+
])
|
|
187
219
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
188
220
|
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
189
221
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [inProcessByUser])
|
|
190
222
|
const deletes = [run(DELETE.from({ ref: query.DELETE.from.ref }))]
|
|
191
223
|
if (draft)
|
|
192
224
|
deletes.push(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
})
|
|
197
|
-
)
|
|
225
|
+
DELETE.from(req.target.drafts).where({
|
|
226
|
+
DraftAdministrativeData_DraftUUID: draft.DraftAdministrativeData_DraftUUID
|
|
227
|
+
})
|
|
198
228
|
)
|
|
199
229
|
if (draft && req.target['@Common.DraftRoot.ActivationAction'])
|
|
200
230
|
deletes.push(
|
|
@@ -214,18 +244,19 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
214
244
|
const targetDraft = req.target.drafts
|
|
215
245
|
const targetWhere = query.SELECT.from.ref[0].where
|
|
216
246
|
const cols = expandStarStar(targetDraft)
|
|
247
|
+
// Use `run` (since also etags might need to be checked)
|
|
248
|
+
// REVISIT: Find a better approach (`etag` as part of CQN?)
|
|
217
249
|
const res = await run(
|
|
218
250
|
SELECT.one
|
|
219
|
-
.from(
|
|
251
|
+
.from({ ref: _redirectRefToDrafts(query.SELECT.from.ref, this.model) })
|
|
220
252
|
.columns(cols)
|
|
221
253
|
.columns([
|
|
222
254
|
'HasActiveEntity',
|
|
223
255
|
'DraftAdministrativeData_DraftUUID',
|
|
224
256
|
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
225
257
|
])
|
|
226
|
-
.where(targetWhere)
|
|
227
258
|
)
|
|
228
|
-
if (!res) req.reject(404)
|
|
259
|
+
if (!res) req.reject(_etagValidationType ? 412 : 404)
|
|
229
260
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
230
261
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
231
262
|
const DraftAdministrativeData_DraftUUID = res.DraftAdministrativeData_DraftUUID
|
|
@@ -236,14 +267,15 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
236
267
|
delete res.IsActiveEntity
|
|
237
268
|
// First run the handlers as they might need access to DraftAdministrativeData or the draft entities
|
|
238
269
|
const result = await run(
|
|
239
|
-
HasActiveEntity ? UPDATE(req.target).data(res).where(targetWhere) : INSERT.into(req.target).entries(res)
|
|
270
|
+
HasActiveEntity ? UPDATE(req.target).data(res).where(targetWhere) : INSERT.into(req.target).entries(res),
|
|
271
|
+
{ headers: { 'if-match': '*' } }
|
|
240
272
|
)
|
|
241
273
|
await _promiseAll([
|
|
242
|
-
|
|
274
|
+
DELETE.from(targetDraft).where(targetWhere),
|
|
243
275
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: DraftAdministrativeData_DraftUUID })
|
|
244
276
|
])
|
|
245
277
|
|
|
246
|
-
req?._?.odataRes?.setStatusCode(201)
|
|
278
|
+
if (!HasActiveEntity) req?._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
247
279
|
|
|
248
280
|
return Object.assign(result, { IsActiveEntity: true })
|
|
249
281
|
|
|
@@ -256,35 +288,24 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
256
288
|
const rootQuery = query.clone()
|
|
257
289
|
rootQuery.SELECT.columns = [{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }]
|
|
258
290
|
rootQuery.SELECT.one = true
|
|
259
|
-
const root = await
|
|
291
|
+
const root = await rootQuery
|
|
260
292
|
if (!root) req.reject(404)
|
|
261
293
|
if (root.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id) req.reject(403)
|
|
262
|
-
const _req = _newReq(req, query, draftParams, req.event)
|
|
294
|
+
const _req = _newReq(req, query, draftParams, { event: req.event })
|
|
263
295
|
const result = await handle(_req)
|
|
264
296
|
return result
|
|
265
297
|
}
|
|
266
298
|
|
|
267
299
|
if (req.event === 'PATCH') {
|
|
268
|
-
if (draftParams.IsActiveEntity) req.reject(501)
|
|
269
|
-
if (!('IsActiveEntity' in draftParams)) {
|
|
270
|
-
const res = await run(
|
|
271
|
-
SELECT.one.from({ ref: req.UPDATE.entity.ref }).columns('DraftAdministrativeData_DraftUUID')
|
|
272
|
-
)
|
|
273
|
-
if (res) req.reject(403, 'DRAFT_ALREADY_EXISTS')
|
|
274
|
-
const _req = _newReq(req, query, draftParams, 'UPDATE')
|
|
275
|
-
const result = await handle(_req)
|
|
276
|
-
return result
|
|
277
|
-
}
|
|
300
|
+
if (draftParams.IsActiveEntity || !('IsActiveEntity' in draftParams)) req.reject(501)
|
|
278
301
|
if (draftParams.IsActiveEntity === false) {
|
|
279
302
|
LOG.debug('patch draft')
|
|
280
303
|
if (req.target?.name.endsWith('DraftAdministrativeData')) req.reject(405)
|
|
281
304
|
const draftsRef = _redirectRefToDrafts(query.UPDATE.entity.ref, this.model)
|
|
282
|
-
const res = await
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
})
|
|
287
|
-
)
|
|
305
|
+
const res = await SELECT.one.from({ ref: draftsRef }).columns('DraftAdministrativeData_DraftUUID', {
|
|
306
|
+
ref: ['DraftAdministrativeData'],
|
|
307
|
+
expand: [{ ref: ['InProcessByUser'] }]
|
|
308
|
+
})
|
|
288
309
|
if (!res) req.reject(404)
|
|
289
310
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
290
311
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [res.DraftAdministrativeData?.InProcessByUser])
|
|
@@ -304,36 +325,11 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
304
325
|
}
|
|
305
326
|
}
|
|
306
327
|
|
|
307
|
-
if (req.event === '
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
) {
|
|
313
|
-
req.query = query
|
|
314
|
-
return handle(req)
|
|
315
|
-
}
|
|
316
|
-
const read = req.query._target.name.endsWith('.drafts')
|
|
317
|
-
? Read.ownDrafts
|
|
318
|
-
: draftParams.IsActiveEntity === false && draftParams.SiblingEntity_IsActiveEntity === null
|
|
319
|
-
? Read.all
|
|
320
|
-
: draftParams.IsActiveEntity === true &&
|
|
321
|
-
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
322
|
-
(draftParams.DraftAdministrativeData_InProcessByUser === 'not null' ||
|
|
323
|
-
draftParams.DraftAdministrativeData_InProcessByUser === 'not ')
|
|
324
|
-
? Read.lockedByAnotherUser
|
|
325
|
-
: draftParams.IsActiveEntity === true &&
|
|
326
|
-
draftParams.SiblingEntity_IsActiveEntity === null &&
|
|
327
|
-
draftParams.DraftAdministrativeData_InProcessByUser === ''
|
|
328
|
-
? Read.unsavedChangesByAnotherUser
|
|
329
|
-
: draftParams.IsActiveEntity === true && draftParams.HasDraftEntity === false
|
|
330
|
-
? Read.unchanged
|
|
331
|
-
: draftParams.IsActiveEntity === true
|
|
332
|
-
? Read.onlyActives
|
|
333
|
-
: draftParams.IsActiveEntity === false
|
|
334
|
-
? Read.ownDrafts
|
|
335
|
-
: Read.onlyActives
|
|
336
|
-
const result = await read(run, query)
|
|
328
|
+
if (req.event === 'STREAM' && draftParams.IsActiveEntity === false) {
|
|
329
|
+
if (query.STREAM.into?.ref) query.STREAM.into.ref = _redirectRefToDrafts(query.STREAM.into.ref, this.model)
|
|
330
|
+
else if (query.STREAM.from?.ref) query.STREAM.from.ref = _redirectRefToDrafts(query.STREAM.from.ref, this.model)
|
|
331
|
+
const _req = _newReq(req, query, draftParams, { event: req.event })
|
|
332
|
+
const result = await handle(_req)
|
|
337
333
|
return result
|
|
338
334
|
}
|
|
339
335
|
|
|
@@ -387,7 +383,7 @@ const Read = {
|
|
|
387
383
|
if (ignoreDrafts) drafts = []
|
|
388
384
|
else {
|
|
389
385
|
try {
|
|
390
|
-
drafts = await Read.complementaryDrafts(
|
|
386
|
+
drafts = await Read.complementaryDrafts(query, actives)
|
|
391
387
|
} catch (e) {
|
|
392
388
|
drafts = []
|
|
393
389
|
}
|
|
@@ -414,7 +410,7 @@ const Read = {
|
|
|
414
410
|
draftsQuery.SELECT.orderBy = undefined
|
|
415
411
|
draftsQuery.SELECT.columns = keys.map(k => ({ ref: [k] }))
|
|
416
412
|
|
|
417
|
-
const drafts = await
|
|
413
|
+
const drafts = await draftsQuery
|
|
418
414
|
const res = await Read.onlyActives(run, query.where(Read.whereNotIn(query._target, drafts)), {
|
|
419
415
|
ignoreDrafts: true
|
|
420
416
|
})
|
|
@@ -498,7 +494,7 @@ const Read = {
|
|
|
498
494
|
_fillIsActiveEntity(row, false, query._drafts._target)
|
|
499
495
|
})
|
|
500
496
|
Read.delete(query._target, actives, ownEditDrafts)
|
|
501
|
-
const otherEditDrafts = await Read.complementaryDrafts(
|
|
497
|
+
const otherEditDrafts = await Read.complementaryDrafts(query, actives)
|
|
502
498
|
Read.merge(query._target, actives, otherEditDrafts, (row, other) => {
|
|
503
499
|
if (other) {
|
|
504
500
|
Object.assign(row, {
|
|
@@ -537,7 +533,7 @@ const Read = {
|
|
|
537
533
|
[isLocked ? '>' : '<']: _lock.shiftedNow
|
|
538
534
|
}
|
|
539
535
|
})
|
|
540
|
-
const drafts = await
|
|
536
|
+
const drafts = await draftsQuery
|
|
541
537
|
const actives = drafts.length
|
|
542
538
|
? await run(query.where(Read.whereIn(query._target, drafts)))
|
|
543
539
|
: Object.assign([], { $count: 0 })
|
|
@@ -561,13 +557,12 @@ const Read = {
|
|
|
561
557
|
const keys = Object_keys(target.keys).filter(k => k !== 'IsActiveEntity')
|
|
562
558
|
const dataArray = data ? (Array.isArray(data) ? data : [data]) : []
|
|
563
559
|
if (not && !dataArray.length) return []
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
]
|
|
560
|
+
const left = { list: keys.map(k => ({ ref: [k] })) }
|
|
561
|
+
const op = not ? ['not', 'in'] : ['in']
|
|
562
|
+
const right = { list: dataArray.map(r => ({ list: keys.map(k => ({ val: r[k] })) })) }
|
|
563
|
+
return [left, ...op, right]
|
|
569
564
|
},
|
|
570
|
-
complementaryDrafts: (
|
|
565
|
+
complementaryDrafts: (query, _actives) => {
|
|
571
566
|
const actives = Array.isArray(_actives) ? _actives : [_actives]
|
|
572
567
|
if (!actives.length) return []
|
|
573
568
|
const drafts = cds.ql.clone(query._drafts)
|
|
@@ -590,7 +585,7 @@ const Read = {
|
|
|
590
585
|
drafts.SELECT.count = undefined
|
|
591
586
|
drafts.SELECT.search = undefined
|
|
592
587
|
drafts.SELECT.one = undefined
|
|
593
|
-
return
|
|
588
|
+
return drafts
|
|
594
589
|
},
|
|
595
590
|
_makeArray: data => (Array.isArray(data) ? data : data ? [data] : []),
|
|
596
591
|
_index: (target, data) => {
|
|
@@ -656,7 +651,7 @@ function _cleanseCols(columns, elements, target) {
|
|
|
656
651
|
}
|
|
657
652
|
|
|
658
653
|
/**
|
|
659
|
-
* Creates a clone of the query, cleanses and collects all draft parameters into .
|
|
654
|
+
* Creates a clone of the query, cleanses and collects all draft parameters into DRAFT_PARAMS.
|
|
660
655
|
*/
|
|
661
656
|
function _cleansed(query, model) {
|
|
662
657
|
const draftParams = {} //> used to collect draft filter criteria
|
|
@@ -680,7 +675,7 @@ function _cleansed(query, model) {
|
|
|
680
675
|
} else if (draftsQuery._target?.name.endsWith('.drafts')) {
|
|
681
676
|
draftsQuery.SELECT.columns = _tweakAdminExpand(draftsQuery.SELECT.columns)
|
|
682
677
|
}
|
|
683
|
-
|
|
678
|
+
draftsQuery[DRAFT_PARAMS] = draftParams
|
|
684
679
|
Object.defineProperty(q, '_drafts', { value: draftsQuery })
|
|
685
680
|
return draftsQuery
|
|
686
681
|
}
|
|
@@ -692,7 +687,7 @@ function _cleansed(query, model) {
|
|
|
692
687
|
})
|
|
693
688
|
}
|
|
694
689
|
|
|
695
|
-
|
|
690
|
+
q[DRAFT_PARAMS] = draftParams
|
|
696
691
|
q[original] = query
|
|
697
692
|
return q
|
|
698
693
|
|
|
@@ -700,8 +695,14 @@ function _cleansed(query, model) {
|
|
|
700
695
|
const target = query._target
|
|
701
696
|
const q = cds.ql.clone(query)
|
|
702
697
|
|
|
703
|
-
const ref =
|
|
704
|
-
|
|
698
|
+
const ref =
|
|
699
|
+
q.SELECT?.from.ref ||
|
|
700
|
+
q.UPDATE?.entity.ref ||
|
|
701
|
+
q.INSERT?.into.ref ||
|
|
702
|
+
q.DELETE?.from.ref ||
|
|
703
|
+
q.STREAM?.into?.ref ||
|
|
704
|
+
q.STREAM?.from?.ref
|
|
705
|
+
const cqn = q.SELECT || q.UPDATE || q.INSERT || q.DELETE || q.STREAM
|
|
705
706
|
|
|
706
707
|
if (ref) {
|
|
707
708
|
let entity
|
|
@@ -714,6 +715,8 @@ function _cleansed(query, model) {
|
|
|
714
715
|
else if (q.DELETE) q.DELETE.from = { ...q.DELETE.from, ref: cleansedRef }
|
|
715
716
|
else if (q.UPDATE) q.UPDATE.entity = { ...q.UPDATE.entity, ref: cleansedRef }
|
|
716
717
|
else if (q.INSERT) q.INSERT.into = { ...q.INSERT.into, ref: cleansedRef }
|
|
718
|
+
else if (q.STREAM?.from) q.STREAM.from = { ...q.STREAM.from, ref: cleansedRef }
|
|
719
|
+
else if (q.STREAM?.into) q.STREAM.into = { ...q.STREAM.into, ref: cleansedRef }
|
|
717
720
|
|
|
718
721
|
// This only works for simple cases of `SiblingEntity`, e.g. `root(ID=1,IsActiveEntity=false)/SiblingEntity`
|
|
719
722
|
// , check if there are more complicated use cases
|
|
@@ -797,15 +800,17 @@ function _cleansed(query, model) {
|
|
|
797
800
|
function _cleanseWhere(xpr, draftParams) {
|
|
798
801
|
const cleansed = []
|
|
799
802
|
for (let i = 0; i < xpr.length; ++i) {
|
|
800
|
-
let x = xpr[i]
|
|
801
|
-
|
|
803
|
+
let x = xpr[i]
|
|
804
|
+
const e = x.ref?.[0]
|
|
802
805
|
if (DRAFT_ELEMENTS.has(e) && !xpr[i + 2]) {
|
|
803
806
|
continue
|
|
804
807
|
}
|
|
805
808
|
if (DRAFT_ELEMENTS.has(e) && xpr[i + 2]) {
|
|
806
809
|
let { val } = xpr[i + 2]
|
|
807
810
|
draftParams[x.ref.join('_')] = xpr[i + 1] === '!=' ? (typeof val === 'boolean' ? !val : 'not ' + val) : val
|
|
808
|
-
i +=
|
|
811
|
+
i += 2
|
|
812
|
+
const last = cleansed[cleansed.length - 1]
|
|
813
|
+
if (last === 'and' || last === 'or') cleansed.pop()
|
|
809
814
|
continue
|
|
810
815
|
}
|
|
811
816
|
if (x.xpr) {
|
|
@@ -817,6 +822,8 @@ function _cleansed(query, model) {
|
|
|
817
822
|
}
|
|
818
823
|
cleansed.push(x)
|
|
819
824
|
}
|
|
825
|
+
const first = cleansed[0]
|
|
826
|
+
if (first === 'and' || first === 'or') cleansed.shift()
|
|
820
827
|
const last = cleansed[cleansed.length - 1]
|
|
821
828
|
if (last === 'and' || last === 'or') cleansed.pop()
|
|
822
829
|
if (cleansed.length) return cleansed
|
|
@@ -849,28 +856,26 @@ function expandStarStar(target, recursion = new Map()) {
|
|
|
849
856
|
|
|
850
857
|
async function onNew(req) {
|
|
851
858
|
LOG.debug('new draft')
|
|
852
|
-
const
|
|
859
|
+
const isDirectAccess = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
|
|
853
860
|
// Only allowed for pseudo draft roots (entities with this action)
|
|
854
|
-
if (
|
|
861
|
+
if (isDirectAccess && !req.target.actives['@Common.DraftRoot.ActivationAction'])
|
|
855
862
|
req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
856
863
|
let DraftUUID
|
|
857
|
-
if (
|
|
864
|
+
if (isDirectAccess) DraftUUID = cds.utils.uuid()
|
|
858
865
|
else {
|
|
859
|
-
const rootData = await
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
.where(req.query.INSERT.into.ref[0].where)
|
|
866
|
-
})
|
|
866
|
+
const rootData = await SELECT.one(req.query.INSERT.into.ref[0].id)
|
|
867
|
+
.columns([
|
|
868
|
+
{ ref: ['DraftAdministrativeData_DraftUUID'] },
|
|
869
|
+
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
870
|
+
])
|
|
871
|
+
.where(req.query.INSERT.into.ref[0].where)
|
|
867
872
|
if (!rootData) req.reject(404)
|
|
868
873
|
if (rootData.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
869
874
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [rootData.DraftAdministrativeData.InProcessByUser])
|
|
870
875
|
DraftUUID = rootData.DraftAdministrativeData_DraftUUID
|
|
871
876
|
}
|
|
872
877
|
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
873
|
-
const adminDataCQN =
|
|
878
|
+
const adminDataCQN = isDirectAccess
|
|
874
879
|
? INSERT.into('DRAFT.DraftAdministrativeData').entries({
|
|
875
880
|
DraftUUID,
|
|
876
881
|
CreationDateTime: timestamp,
|
|
@@ -889,36 +894,38 @@ async function onNew(req) {
|
|
|
889
894
|
})
|
|
890
895
|
.where({ DraftUUID })
|
|
891
896
|
|
|
892
|
-
const
|
|
897
|
+
const _setDraftUUIDandHasActiveEntity = (obj, target) => {
|
|
893
898
|
const newObj = Object.assign({}, obj, { DraftAdministrativeData_DraftUUID: DraftUUID, HasActiveEntity: false })
|
|
894
899
|
if (!target) return newObj
|
|
895
900
|
|
|
896
901
|
// Also support deep insertions
|
|
897
902
|
for (const key in newObj) {
|
|
898
903
|
if (!target.elements[key]?.isComposition) continue
|
|
904
|
+
// do array trick
|
|
899
905
|
if (Array.isArray(newObj[key]))
|
|
900
|
-
newObj[key] = newObj[key].map(v =>
|
|
906
|
+
newObj[key] = newObj[key].map(v => _setDraftUUIDandHasActiveEntity(v, target.elements[key]._target))
|
|
901
907
|
else if (typeof newObj[key] === 'object') {
|
|
902
|
-
newObj[key] =
|
|
908
|
+
newObj[key] = _setDraftUUIDandHasActiveEntity(newObj[key], target.elements[key]._target)
|
|
903
909
|
}
|
|
904
910
|
}
|
|
905
911
|
|
|
906
912
|
return newObj
|
|
907
913
|
}
|
|
908
914
|
|
|
909
|
-
const draftData =
|
|
915
|
+
const draftData = _setDraftUUIDandHasActiveEntity(req.query.INSERT.entries[0], req.target)
|
|
910
916
|
|
|
911
917
|
delete draftData.IsActiveEntity
|
|
912
918
|
const draftCQN = INSERT.into(req.target).entries(draftData)
|
|
913
919
|
|
|
914
|
-
await _promiseAll([
|
|
920
|
+
await _promiseAll([adminDataCQN, this.run(draftCQN)])
|
|
915
921
|
req._.readAfterWrite = true
|
|
916
922
|
return { ...draftData, IsActiveEntity: false }
|
|
917
923
|
}
|
|
918
924
|
|
|
919
925
|
async function onEdit(req) {
|
|
920
926
|
LOG.debug('edit active')
|
|
921
|
-
|
|
927
|
+
// use symbol for _draftParams
|
|
928
|
+
const draftParams = req.query[DRAFT_PARAMS]
|
|
922
929
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== true) {
|
|
923
930
|
req.reject(400, 'Action "draftEdit" can only be called on the root active entity')
|
|
924
931
|
}
|
|
@@ -928,6 +935,7 @@ async function onEdit(req) {
|
|
|
928
935
|
|
|
929
936
|
const DraftUUID = cds.utils.uuid()
|
|
930
937
|
|
|
938
|
+
// REVISIT: Later optimization if datasource === db: INSERT FROM SELECT
|
|
931
939
|
const cols = expandStarStar(req.target)
|
|
932
940
|
const _addDraftColumns = (target, columns) => {
|
|
933
941
|
if (target.drafts) {
|
|
@@ -947,22 +955,24 @@ async function onEdit(req) {
|
|
|
947
955
|
.columns({ ref: ['DraftAdministrativeData'], expand: [_inProcessByUserXpr(_lock.shiftedNow)] })
|
|
948
956
|
.where(targetWhere)
|
|
949
957
|
// prevent service to check for own user
|
|
950
|
-
|
|
958
|
+
existingDraft[DRAFT_PARAMS] = draftParams
|
|
951
959
|
|
|
952
960
|
const activeCQN = SELECT.one.from(req.target).columns(cols).where(targetWhere)
|
|
953
961
|
activeCQN._suppressLocalization = true // in the future we should be able to just set activeCQN.SELECT.localized = false
|
|
954
962
|
|
|
955
963
|
const activeCheck = SELECT.one(req.target).columns([1]).where(targetWhere).forUpdate()
|
|
956
|
-
|
|
964
|
+
activeCheck[DRAFT_PARAMS] = draftParams
|
|
957
965
|
// It's not possible to use `FOR UPDATE` in HANA if the view contains joins/unions. Unfortunately, we can't resolve the table entity
|
|
958
966
|
// because we must trigger the app-service request on the target entity (which could be delegated to a remote service).
|
|
959
967
|
// The best we can do is to catch a potential error
|
|
960
|
-
|
|
968
|
+
try {
|
|
969
|
+
await activeCheck
|
|
970
|
+
} catch {} // eslint-disable-line no-empty
|
|
961
971
|
|
|
962
972
|
const [res, draft] = await _promiseAll([
|
|
963
|
-
|
|
973
|
+
this._datasource.run(activeCQN),
|
|
964
974
|
// no user check must be done here...
|
|
965
|
-
|
|
975
|
+
existingDraft
|
|
966
976
|
])
|
|
967
977
|
|
|
968
978
|
if (!res) req.reject(404)
|
|
@@ -974,7 +984,7 @@ async function onEdit(req) {
|
|
|
974
984
|
for (const key in req.target.drafts.keys) keys[key] = res[key]
|
|
975
985
|
await _promiseAll([
|
|
976
986
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID }),
|
|
977
|
-
|
|
987
|
+
DELETE.from(req.target.drafts).where(keys)
|
|
978
988
|
])
|
|
979
989
|
}
|
|
980
990
|
|
|
@@ -995,11 +1005,12 @@ async function onEdit(req) {
|
|
|
995
1005
|
res.DraftAdministrativeData_DraftUUID = DraftUUID
|
|
996
1006
|
res.HasActiveEntity = true
|
|
997
1007
|
delete res.DraftAdministrativeData
|
|
998
|
-
|
|
1008
|
+
// change to db run
|
|
1009
|
+
await INSERT.into(targetDraft).entries(res)
|
|
999
1010
|
|
|
1000
1011
|
// REVISIT: we need to use okra API here because it must be set in the batched request
|
|
1001
1012
|
// status code must be set in handler to allow overriding for FE V2
|
|
1002
|
-
req?._?.odataRes?.setStatusCode(201)
|
|
1013
|
+
req?._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
1003
1014
|
|
|
1004
1015
|
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
1005
1016
|
}
|
|
@@ -1007,21 +1018,21 @@ async function onEdit(req) {
|
|
|
1007
1018
|
async function onCancel(req) {
|
|
1008
1019
|
LOG.debug('delete draft')
|
|
1009
1020
|
const activeRef = _redirectRefToActives(req.query.DELETE.from.ref, this.model)
|
|
1010
|
-
const draftParams = req.query
|
|
1021
|
+
const draftParams = req.query[DRAFT_PARAMS]
|
|
1011
1022
|
|
|
1012
|
-
const
|
|
1023
|
+
const draftQuery = SELECT.one
|
|
1013
1024
|
.from({ ref: req.query.DELETE.from.ref })
|
|
1014
1025
|
.columns([
|
|
1015
1026
|
'DraftAdministrativeData_DraftUUID',
|
|
1016
1027
|
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
1017
1028
|
])
|
|
1029
|
+
if (req._etagValidationClause) draftQuery.where(req._etagValidationClause)
|
|
1018
1030
|
// do not add InProcessByUser restriction
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
if (draftParams.IsActiveEntity === false && !draft) req.reject(404)
|
|
1031
|
+
const draft = await draftQuery
|
|
1032
|
+
if (draftParams.IsActiveEntity === false && !draft) req.reject(req._etagValidationType ? 412 : 404)
|
|
1022
1033
|
if (draft && draft.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
1023
1034
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [draft.DraftAdministrativeData?.InProcessByUser])
|
|
1024
|
-
const queries = !draft ? [] : [
|
|
1035
|
+
const queries = !draft ? [] : [this.run(DELETE.from({ ref: req.query.DELETE.from.ref }))]
|
|
1025
1036
|
if (draft && req.target['@Common.DraftRoot.ActivationAction'])
|
|
1026
1037
|
// only for draft root
|
|
1027
1038
|
queries.push(
|
|
@@ -1037,14 +1048,14 @@ async function onCancel(req) {
|
|
|
1037
1048
|
})
|
|
1038
1049
|
.where({ DraftUUID: draft.DraftAdministrativeData_DraftUUID })
|
|
1039
1050
|
)
|
|
1040
|
-
if (draftParams.IsActiveEntity) queries.push(
|
|
1051
|
+
if (draftParams.IsActiveEntity) queries.push(this.run(DELETE.from({ ref: activeRef })))
|
|
1041
1052
|
await _promiseAll(queries)
|
|
1042
1053
|
return req.data
|
|
1043
1054
|
}
|
|
1044
1055
|
|
|
1045
1056
|
async function onPrepare(req) {
|
|
1046
1057
|
LOG.debug('prepare draft')
|
|
1047
|
-
const draftParams = req.query
|
|
1058
|
+
const draftParams = req.query[DRAFT_PARAMS]
|
|
1048
1059
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== false) {
|
|
1049
1060
|
req.reject(400, 'Action "draftPrepare" can only be called on the root draft entity')
|
|
1050
1061
|
}
|
|
@@ -1057,8 +1068,8 @@ async function onPrepare(req) {
|
|
|
1057
1068
|
})
|
|
1058
1069
|
.columns(keys)
|
|
1059
1070
|
.where(where)
|
|
1060
|
-
|
|
1061
|
-
const data = await
|
|
1071
|
+
draftQuery[DRAFT_PARAMS] = draftParams
|
|
1072
|
+
const data = await draftQuery
|
|
1062
1073
|
if (!data) req.reject(404)
|
|
1063
1074
|
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1064
1075
|
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [data.DraftAdministrativeData?.InProcessByUser])
|
|
@@ -1066,4 +1077,48 @@ async function onPrepare(req) {
|
|
|
1066
1077
|
return { ...data, IsActiveEntity: false }
|
|
1067
1078
|
}
|
|
1068
1079
|
|
|
1069
|
-
module.exports = {
|
|
1080
|
+
module.exports = {
|
|
1081
|
+
impl() {
|
|
1082
|
+
if (!this._datasource) this._datasource = cds.db
|
|
1083
|
+
function _wrapped(handler, isActiveEntity) {
|
|
1084
|
+
const fn = function (req, next) {
|
|
1085
|
+
if (!req.target?.drafts || (isActiveEntity && req.target.isDraft) || (!isActiveEntity && !req.target.isDraft))
|
|
1086
|
+
return next.call(this)
|
|
1087
|
+
return handler.call(this, req, next)
|
|
1088
|
+
}
|
|
1089
|
+
return fn
|
|
1090
|
+
}
|
|
1091
|
+
// Also runs those handlers if they're annotated with @odata.draft.enabled through extensibility
|
|
1092
|
+
this.on('NEW', '*', _wrapped(onNew, false))
|
|
1093
|
+
this.on('EDIT', '*', _wrapped(onEdit, true))
|
|
1094
|
+
this.on('CANCEL', '*', _wrapped(onCancel, false))
|
|
1095
|
+
this.on('draftPrepare', '*', _wrapped(onPrepare, false))
|
|
1096
|
+
|
|
1097
|
+
async function fioriReadCompat(req, next) {
|
|
1098
|
+
if (!req.target) return next()
|
|
1099
|
+
const data = await next()
|
|
1100
|
+
if (!data) return data
|
|
1101
|
+
let _key
|
|
1102
|
+
for (const key in req.target.keys) {
|
|
1103
|
+
if (key === 'IsActiveEntity') continue
|
|
1104
|
+
_key = key
|
|
1105
|
+
break
|
|
1106
|
+
}
|
|
1107
|
+
const _addIsActiveEntity = (data, IsActiveEntity) => {
|
|
1108
|
+
if (Array.isArray(data)) return data.map(d => _addIsActiveEntity(d, IsActiveEntity))
|
|
1109
|
+
if (_key in data) data.IsActiveEntity = IsActiveEntity
|
|
1110
|
+
}
|
|
1111
|
+
_addIsActiveEntity(data, !req.target?.isDraft)
|
|
1112
|
+
return data
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (cds.env.fiori.draft_compat) {
|
|
1116
|
+
// register after read handlers to add `IsActiveEntity`,
|
|
1117
|
+
// so stakeholders have access to it when calling next()
|
|
1118
|
+
|
|
1119
|
+
// to check if data contains a key value
|
|
1120
|
+
this.on('READ', '*', _wrapped(fioriReadCompat, true))
|
|
1121
|
+
this.on('READ', '*', _wrapped(fioriReadCompat, false))
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|