json-rest-api 1.0.23 → 1.0.25
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/README.md +24 -2
- package/docs/API.md +157 -33
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +88 -6
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +3 -3
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +2 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1 -1
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +63 -0
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +17 -12
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +122 -2
- package/docs/GUIDE/GUIDE_X_Row_Policies.md +603 -0
- package/docs/GUIDE/GUIDE_X_SocketIO.md +15 -10
- package/docs/GUIDE/GUIDE_X_URL_Management.md +93 -254
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +2 -0
- package/docs/GUIDE/index.md +4 -1
- package/fixing_plan.md +112 -0
- package/index.js +2 -0
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +31 -38
- package/plugins/core/connectors/express-plugin.js +6 -7
- package/plugins/core/connectors/fastify-plugin.js +5 -1
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +17 -11
- package/plugins/core/connectors/lib/transport-route-schemas.js +12 -60
- package/plugins/core/file-handling-plugin.js +126 -96
- package/plugins/core/lib/anyapi/anyapi-registry.js +21 -3
- package/plugins/core/lib/anyapi/schema-utils.js +44 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +11 -2
- package/plugins/core/lib/dbIntrospection.js +1 -1
- package/plugins/core/lib/dbTablesOperations.js +10 -3
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +20 -16
- package/plugins/core/lib/querying/knex-pagination-helpers.js +38 -84
- package/plugins/core/lib/querying/knex-query-helpers.js +5 -6
- package/plugins/core/lib/querying/knex-relationship-includes.js +40 -46
- package/plugins/core/lib/querying/knex-window-queries.js +10 -77
- package/plugins/core/lib/querying/url-helpers.js +9 -21
- package/plugins/core/lib/querying-writing/compile-schemas.js +10 -6
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +51 -0
- package/plugins/core/lib/querying-writing/database-capabilities.js +1 -1
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +18 -9
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +7 -3
- package/plugins/core/lib/querying-writing/relationship-contracts.js +56 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +53 -4
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/return-record-settings.js +47 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +34 -43
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +24 -3
- package/plugins/core/lib/storage/storage-adapter.js +76 -14
- package/plugins/core/lib/storage/storage-mapping.js +86 -16
- package/plugins/core/lib/writing/many-to-many-manipulations.js +8 -5
- package/plugins/core/lib/writing/relationship-processor.js +10 -1
- package/plugins/core/rest-api-anyapi-knex-plugin.js +402 -197
- package/plugins/core/rest-api-knex-plugin.js +93 -76
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +9 -2
- package/plugins/core/rest-api-plugin-methods/common.js +112 -72
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +34 -15
- package/plugins/core/rest-api-plugin-methods/delete.js +8 -7
- package/plugins/core/rest-api-plugin-methods/get-related.js +22 -15
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +8 -3
- package/plugins/core/rest-api-plugin-methods/get.js +7 -1
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +26 -8
- package/plugins/core/rest-api-plugin-methods/patch.js +34 -9
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +35 -16
- package/plugins/core/rest-api-plugin-methods/post.js +12 -7
- package/plugins/core/rest-api-plugin-methods/put.js +35 -10
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +11 -37
- package/plugins/core/rest-api-positioning-plugin.js +1 -4
- package/plugins/core/rest-api-row-policy-plugin.js +157 -0
- package/plugins/core/socketio-plugin.js +0 -1
- package/plugins/storage/local-storage.js +45 -20
- package/plugins/storage/s3-storage.js +23 -54
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +8 -1
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/anyapi-cursor-pagination.test.js +7 -1
- package/tests/anyapi-custom-idproperty.test.js +163 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/db-introspection.test.js +4 -1
- package/tests/db-table-operations.test.js +25 -20
- package/tests/fastify-plugin.test.js +49 -6
- package/tests/field-setters.test.js +10 -10
- package/tests/file-handling.test.js +118 -0
- package/tests/fixtures/api-configs.js +495 -4
- package/tests/full-jsonapi-belongsto-linkage.test.js +150 -0
- package/tests/http-connectors-parity.test.js +165 -5
- package/tests/id-normalization.test.js +444 -0
- package/tests/local-storage.test.js +96 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +132 -11
- package/tests/pagination-enhanced.test.js +66 -15
- package/tests/post-commit-hooks.test.js +120 -0
- package/tests/public-exports.test.js +15 -0
- package/tests/put-create-parity.test.js +216 -0
- package/tests/relationship-endpoints.test.js +259 -0
- package/tests/return-record-settings.test.js +184 -0
- package/tests/row-policy.test.js +433 -0
- package/tests/s3-storage.test.js +38 -0
- package/tests/searchschema-merge.test.js +17 -0
- package/tests/socketio.test.js +62 -0
- package/tests/storage-mapping.test.js +56 -6
- package/tests/test-development-guide.md +5 -5
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import { RestApiResourceError, RestApiValidationError
|
|
2
|
-
import {
|
|
1
|
+
import { RestApiResourceError, RestApiValidationError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
import {
|
|
3
|
+
commitOwnedTransaction,
|
|
4
|
+
findRelationshipDefinition,
|
|
5
|
+
getVisibleRelationshipParent,
|
|
6
|
+
handleWriteMethodError,
|
|
7
|
+
validateRelationshipRoutePayload
|
|
8
|
+
} from './common.js'
|
|
9
|
+
import {
|
|
10
|
+
normalizeRelationshipIdentifiers,
|
|
11
|
+
requireExistingResourceId
|
|
12
|
+
} from '../lib/querying-writing/resource-id-normalization.js'
|
|
3
13
|
|
|
4
14
|
/**
|
|
5
15
|
* DELETE RELATIONSHIP
|
|
@@ -11,9 +21,13 @@ import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
|
|
|
11
21
|
* @param {array} relationshipData - Array of resource identifiers to remove
|
|
12
22
|
* @returns {Promise<void>} 204 No Content
|
|
13
23
|
*/
|
|
14
|
-
export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
|
|
24
|
+
export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
15
25
|
context.method = 'deleteRelationship'
|
|
16
|
-
context.id = params.id
|
|
26
|
+
context.id = requireExistingResourceId(params.id, {
|
|
27
|
+
scopeOptions,
|
|
28
|
+
vars,
|
|
29
|
+
scopeName
|
|
30
|
+
})
|
|
17
31
|
context.relationshipName = params.relationshipName
|
|
18
32
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
33
|
|
|
@@ -40,21 +54,28 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
40
54
|
)
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
params.relationshipData = validateRelationshipRoutePayload({
|
|
58
|
+
context,
|
|
59
|
+
vars,
|
|
60
|
+
scopeName,
|
|
61
|
+
operation: 'deleteRelationship',
|
|
62
|
+
relationshipData: params.relationshipData
|
|
63
|
+
})
|
|
64
|
+
params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
|
|
46
65
|
|
|
47
66
|
// Check permissions
|
|
48
67
|
await runHooks('checkPermissions')
|
|
49
68
|
await runHooks('checkPermissionsDeleteRelationship')
|
|
50
69
|
|
|
51
|
-
// Verify parent exists
|
|
52
|
-
const
|
|
70
|
+
// Verify the parent exists in the caller's visible dataset.
|
|
71
|
+
const parentRecord = await getVisibleRelationshipParent({
|
|
72
|
+
context,
|
|
73
|
+
helpers,
|
|
53
74
|
scopeName,
|
|
54
|
-
|
|
75
|
+
runHooks
|
|
55
76
|
})
|
|
56
77
|
|
|
57
|
-
if (!
|
|
78
|
+
if (!parentRecord) {
|
|
58
79
|
throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
59
80
|
}
|
|
60
81
|
|
|
@@ -99,16 +120,14 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
99
120
|
},
|
|
100
121
|
transaction: context.transaction,
|
|
101
122
|
simplified: false
|
|
102
|
-
})
|
|
123
|
+
}, { ...context })
|
|
103
124
|
}
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
await runHooks('finish')
|
|
107
128
|
await runHooks('finishDeleteRelationship')
|
|
108
129
|
|
|
109
|
-
|
|
110
|
-
await context.transaction.commit()
|
|
111
|
-
}
|
|
130
|
+
await commitOwnedTransaction(context, runHooks)
|
|
112
131
|
|
|
113
132
|
// 204 No Content
|
|
114
133
|
} catch (error) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
import { handleWriteMethodError } from './common.js'
|
|
2
|
+
import { commitOwnedTransaction, handleWriteMethodError } from './common.js'
|
|
3
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* DELETE
|
|
@@ -26,7 +27,11 @@ export default async function deleteMethod ({
|
|
|
26
27
|
context.method = 'delete'
|
|
27
28
|
|
|
28
29
|
// Set the ID in context
|
|
29
|
-
context.id = params.id
|
|
30
|
+
context.id = requireExistingResourceId(params.id, {
|
|
31
|
+
scopeOptions,
|
|
32
|
+
vars,
|
|
33
|
+
scopeName
|
|
34
|
+
})
|
|
30
35
|
|
|
31
36
|
// Set scopeName in context (needed for broadcasting)
|
|
32
37
|
context.scopeName = scopeName
|
|
@@ -91,11 +96,7 @@ export default async function deleteMethod ({
|
|
|
91
96
|
await runHooks('finish')
|
|
92
97
|
await runHooks('finishDelete')
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
if (context.shouldCommit) {
|
|
96
|
-
await context.transaction.commit()
|
|
97
|
-
await runHooks('afterCommit')
|
|
98
|
-
}
|
|
99
|
+
await commitOwnedTransaction(context, runHooks)
|
|
99
100
|
|
|
100
101
|
// DELETE typically returns void/undefined (204 No Content)
|
|
101
102
|
} catch (error) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
import { findRelationshipDefinition } from './common.js'
|
|
2
|
+
import { findRelationshipDefinition, getVisibleRelationshipParent } from './common.js'
|
|
3
3
|
import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
4
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET RELATED
|
|
@@ -12,9 +13,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
|
12
13
|
* @param {object} queryParams - Standard query parameters
|
|
13
14
|
* @returns {Promise<object>} Related resources with full data
|
|
14
15
|
*/
|
|
15
|
-
export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
16
|
+
export default async function getRelatedMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
|
|
16
17
|
context.method = 'getRelated'
|
|
17
|
-
context.id = params.id
|
|
18
|
+
context.id = requireExistingResourceId(params.id, {
|
|
19
|
+
scopeOptions,
|
|
20
|
+
vars,
|
|
21
|
+
scopeName
|
|
22
|
+
})
|
|
18
23
|
context.relationshipName = params.relationshipName
|
|
19
24
|
context.queryParams = params.queryParams || {}
|
|
20
25
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
@@ -52,13 +57,15 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
52
57
|
await runHooks('checkPermissions')
|
|
53
58
|
await runHooks('checkPermissionsGetRelated')
|
|
54
59
|
|
|
55
|
-
// Verify parent exists
|
|
56
|
-
const
|
|
60
|
+
// Verify the parent exists in the caller's visible dataset.
|
|
61
|
+
const parentRecord = await getVisibleRelationshipParent({
|
|
62
|
+
context,
|
|
63
|
+
helpers,
|
|
57
64
|
scopeName,
|
|
58
|
-
|
|
65
|
+
runHooks
|
|
59
66
|
})
|
|
60
67
|
|
|
61
|
-
if (!
|
|
68
|
+
if (!parentRecord) {
|
|
62
69
|
throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
63
70
|
}
|
|
64
71
|
|
|
@@ -97,7 +104,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
97
104
|
transaction: context.transaction,
|
|
98
105
|
simplified: false,
|
|
99
106
|
isTransport: params.isTransport
|
|
100
|
-
})
|
|
107
|
+
}, { ...context })
|
|
101
108
|
|
|
102
109
|
const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
|
|
103
110
|
if (!relatedId) {
|
|
@@ -114,7 +121,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
114
121
|
transaction: context.transaction,
|
|
115
122
|
simplified: false,
|
|
116
123
|
isTransport: params.isTransport
|
|
117
|
-
})
|
|
124
|
+
}, { ...context })
|
|
118
125
|
|
|
119
126
|
return {
|
|
120
127
|
links: { self: buildRelationshipUrl(context, scope, scopeName, context.id, context.relationshipName, false) },
|
|
@@ -135,7 +142,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
135
142
|
transaction: context.transaction,
|
|
136
143
|
simplified: false,
|
|
137
144
|
isTransport: params.isTransport
|
|
138
|
-
})
|
|
145
|
+
}, { ...context })
|
|
139
146
|
|
|
140
147
|
// Extract the related resource from the parent's relationships
|
|
141
148
|
const relatedId = parent.data.relationships?.[context.relationshipName]?.data?.id
|
|
@@ -189,7 +196,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
189
196
|
transaction: context.transaction,
|
|
190
197
|
simplified: false,
|
|
191
198
|
isTransport: params.isTransport
|
|
192
|
-
})
|
|
199
|
+
}, { ...context })
|
|
193
200
|
|
|
194
201
|
if (!result.links) {
|
|
195
202
|
result.links = {}
|
|
@@ -223,7 +230,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
223
230
|
transaction: context.transaction,
|
|
224
231
|
simplified: false,
|
|
225
232
|
isTransport: params.isTransport
|
|
226
|
-
})
|
|
233
|
+
}, { ...context })
|
|
227
234
|
|
|
228
235
|
if (!result.links) {
|
|
229
236
|
result.links = {}
|
|
@@ -251,7 +258,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
251
258
|
transaction: context.transaction,
|
|
252
259
|
simplified: false,
|
|
253
260
|
isTransport: params.isTransport,
|
|
254
|
-
})
|
|
261
|
+
}, { ...context })
|
|
255
262
|
if (related?.data) {
|
|
256
263
|
results.push(related.data)
|
|
257
264
|
}
|
|
@@ -302,7 +309,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
302
309
|
transaction: context.transaction,
|
|
303
310
|
simplified: false,
|
|
304
311
|
isTransport: params.isTransport
|
|
305
|
-
})
|
|
312
|
+
}, { ...context })
|
|
306
313
|
let includedResources = pivotResult.included?.filter((r) => r.type === targetType) || []
|
|
307
314
|
|
|
308
315
|
if (includedResources.length === 0) {
|
|
@@ -334,7 +341,7 @@ export default async function getRelatedMethod ({ params, context, vars, helpers
|
|
|
334
341
|
transaction: context.transaction,
|
|
335
342
|
simplified: false,
|
|
336
343
|
isTransport: params.isTransport,
|
|
337
|
-
})
|
|
344
|
+
}, { ...context })
|
|
338
345
|
if (related?.data) {
|
|
339
346
|
includedResources.push(related.data)
|
|
340
347
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { findRelationshipDefinition } from './common.js'
|
|
3
3
|
import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
4
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET RELATIONSHIP
|
|
@@ -11,9 +12,13 @@ import { buildRelationshipUrl } from '../lib/querying/url-helpers.js'
|
|
|
11
12
|
* @param {string} relationshipName - The name of the relationship
|
|
12
13
|
* @returns {Promise<object>} Relationship linkage with links
|
|
13
14
|
*/
|
|
14
|
-
export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
15
|
+
export default async function getRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api }) {
|
|
15
16
|
context.method = 'getRelationship'
|
|
16
|
-
context.id = params.id
|
|
17
|
+
context.id = requireExistingResourceId(params.id, {
|
|
18
|
+
scopeOptions,
|
|
19
|
+
vars,
|
|
20
|
+
scopeName
|
|
21
|
+
})
|
|
17
22
|
context.relationshipName = params.relationshipName
|
|
18
23
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
24
|
|
|
@@ -41,7 +46,7 @@ export default async function getRelationshipMethod ({ params, context, vars, he
|
|
|
41
46
|
transaction: context.transaction,
|
|
42
47
|
simplified: false,
|
|
43
48
|
isTransport: params.isTransport
|
|
44
|
-
})
|
|
49
|
+
}, { ...context })
|
|
45
50
|
|
|
46
51
|
if (!fullRecord || !fullRecord.data) {
|
|
47
52
|
throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
@@ -3,6 +3,7 @@ import { normalizeRecordAttributes } from '../lib/querying-writing/database-valu
|
|
|
3
3
|
import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
|
|
4
4
|
import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
|
|
5
5
|
import { getRequestContracts, validateRequestContractOrThrow } from '../lib/querying-writing/request-contracts.js'
|
|
6
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
6
7
|
import { cascadeConfig } from './common.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -62,10 +63,15 @@ export default async function getMethod ({
|
|
|
62
63
|
includeDepthLimit: vars.includeDepthLimit,
|
|
63
64
|
sortableFields: vars.sortableFields
|
|
64
65
|
})
|
|
66
|
+
const normalizedId = requireExistingResourceId(params.id, {
|
|
67
|
+
scopeOptions,
|
|
68
|
+
vars,
|
|
69
|
+
scopeName
|
|
70
|
+
})
|
|
65
71
|
const validatedRequest = validateRequestContractOrThrow(
|
|
66
72
|
requestContracts.get,
|
|
67
73
|
{
|
|
68
|
-
id:
|
|
74
|
+
id: normalizedId,
|
|
69
75
|
queryParams: context.queryParams
|
|
70
76
|
},
|
|
71
77
|
'GET request parameters are invalid'
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commitOwnedTransaction,
|
|
3
|
+
handleWriteMethodError,
|
|
4
|
+
validateRelationshipRoutePayload
|
|
5
|
+
} from './common.js'
|
|
6
|
+
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* PATCH RELATIONSHIP
|
|
3
10
|
* Completely replaces a relationship
|
|
@@ -8,10 +15,15 @@
|
|
|
8
15
|
* @param {object|array} relationshipData - New relationship data
|
|
9
16
|
* @returns {Promise<void>} 204 No Content
|
|
10
17
|
*/
|
|
11
|
-
export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
18
|
+
export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
12
19
|
context.method = 'patchRelationship'
|
|
13
|
-
context.id = params.id
|
|
20
|
+
context.id = requireExistingResourceId(params.id, {
|
|
21
|
+
scopeOptions,
|
|
22
|
+
vars,
|
|
23
|
+
scopeName
|
|
24
|
+
})
|
|
14
25
|
context.relationshipName = params.relationshipName
|
|
26
|
+
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
15
27
|
|
|
16
28
|
// Transaction handling
|
|
17
29
|
context.transaction = params.transaction ||
|
|
@@ -24,6 +36,14 @@ export default async function patchRelationshipMethod ({ params, context, vars,
|
|
|
24
36
|
await runHooks('checkPermissions')
|
|
25
37
|
await runHooks('checkPermissionsPatchRelationship')
|
|
26
38
|
|
|
39
|
+
const relationshipData = validateRelationshipRoutePayload({
|
|
40
|
+
context,
|
|
41
|
+
vars,
|
|
42
|
+
scopeName,
|
|
43
|
+
operation: 'patchRelationship',
|
|
44
|
+
relationshipData: params.relationshipData
|
|
45
|
+
})
|
|
46
|
+
|
|
27
47
|
// Reuse existing patch with relationship data
|
|
28
48
|
await scope.patch({
|
|
29
49
|
id: context.id,
|
|
@@ -32,24 +52,22 @@ export default async function patchRelationshipMethod ({ params, context, vars,
|
|
|
32
52
|
type: scopeName,
|
|
33
53
|
id: context.id,
|
|
34
54
|
relationships: {
|
|
35
|
-
[params.relationshipName]: { data:
|
|
55
|
+
[params.relationshipName]: { data: relationshipData }
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
},
|
|
39
59
|
transaction: context.transaction,
|
|
40
60
|
simplified: false,
|
|
41
61
|
isTransport: params.isTransport
|
|
42
|
-
})
|
|
62
|
+
}, { ...context })
|
|
43
63
|
|
|
44
64
|
await runHooks('finish')
|
|
45
65
|
await runHooks('finishPatchRelationship')
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
await context.transaction.commit()
|
|
49
|
-
}
|
|
67
|
+
await commitOwnedTransaction(context, runHooks)
|
|
50
68
|
|
|
51
69
|
// 204 No Content
|
|
52
70
|
} catch (error) {
|
|
53
|
-
await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
|
|
71
|
+
await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log, runHooks)
|
|
54
72
|
}
|
|
55
73
|
}
|
|
@@ -3,6 +3,10 @@ import { processRelationships } from '../lib/writing/relationship-processor.js'
|
|
|
3
3
|
import { updateManyToManyRelationship } from '../lib/writing/many-to-many-manipulations.js'
|
|
4
4
|
import { ERROR_SUBTYPES } from '../lib/querying-writing/knex-constants.js'
|
|
5
5
|
import { getRequestContracts, validateRequestContractOrThrow } from '../lib/querying-writing/request-contracts.js'
|
|
6
|
+
import {
|
|
7
|
+
requireDocumentResourceId,
|
|
8
|
+
requireExistingResourceId
|
|
9
|
+
} from '../lib/querying-writing/resource-id-normalization.js'
|
|
6
10
|
import {
|
|
7
11
|
setupCommonRequest,
|
|
8
12
|
validateResourceAttributesBeforeWrite,
|
|
@@ -10,7 +14,8 @@ import {
|
|
|
10
14
|
applyFieldSetters,
|
|
11
15
|
validatePivotResource,
|
|
12
16
|
handleRecordReturnAfterWrite,
|
|
13
|
-
handleWriteMethodError
|
|
17
|
+
handleWriteMethodError,
|
|
18
|
+
commitOwnedTransaction
|
|
14
19
|
} from './common.js'
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -59,14 +64,37 @@ export default async function patchMethod ({
|
|
|
59
64
|
includeDepthLimit: vars.includeDepthLimit,
|
|
60
65
|
sortableFields: vars.sortableFields
|
|
61
66
|
})
|
|
67
|
+
const normalizedPathId = params.id === undefined
|
|
68
|
+
? null
|
|
69
|
+
: requireExistingResourceId(params.id, {
|
|
70
|
+
scopeOptions,
|
|
71
|
+
vars,
|
|
72
|
+
scopeName
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (normalizedPathId && !context.inputRecord?.data?.id) {
|
|
76
|
+
context.inputRecord = {
|
|
77
|
+
...context.inputRecord,
|
|
78
|
+
data: {
|
|
79
|
+
...(context.inputRecord?.data || {}),
|
|
80
|
+
id: normalizedPathId
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
context.inputRecord = validateRequestContractOrThrow(
|
|
63
86
|
requestContracts.patch,
|
|
64
87
|
context.inputRecord,
|
|
65
88
|
'PATCH request body is invalid'
|
|
66
89
|
)
|
|
67
|
-
|
|
90
|
+
const normalizedBodyId = requireDocumentResourceId(context.inputRecord.data.id, {
|
|
91
|
+
scopeOptions,
|
|
92
|
+
vars
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (normalizedPathId && normalizedPathId !== normalizedBodyId) {
|
|
68
96
|
throw new RestApiValidationError(
|
|
69
|
-
`ID mismatch. URL path ID '${
|
|
97
|
+
`ID mismatch. URL path ID '${normalizedPathId}' does not match request body ID '${normalizedBodyId}'`,
|
|
70
98
|
{
|
|
71
99
|
fields: ['data.id'],
|
|
72
100
|
violations: [{
|
|
@@ -77,7 +105,8 @@ export default async function patchMethod ({
|
|
|
77
105
|
}
|
|
78
106
|
)
|
|
79
107
|
}
|
|
80
|
-
context.
|
|
108
|
+
context.inputRecord.data.id = normalizedBodyId
|
|
109
|
+
context.id = normalizedPathId || normalizedBodyId
|
|
81
110
|
|
|
82
111
|
// Validate that user has read access to all related resources
|
|
83
112
|
// This ensures users can only create relationships to resources they can access
|
|
@@ -194,11 +223,7 @@ export default async function patchMethod ({
|
|
|
194
223
|
log
|
|
195
224
|
})
|
|
196
225
|
|
|
197
|
-
|
|
198
|
-
if (context.shouldCommit) {
|
|
199
|
-
await context.transaction.commit()
|
|
200
|
-
await runHooks('afterCommit')
|
|
201
|
-
}
|
|
226
|
+
await commitOwnedTransaction(context, runHooks)
|
|
202
227
|
|
|
203
228
|
return ret
|
|
204
229
|
} catch (error) {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { RestApiResourceError, RestApiValidationError
|
|
2
|
-
import {
|
|
1
|
+
import { RestApiResourceError, RestApiValidationError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
import {
|
|
3
|
+
commitOwnedTransaction,
|
|
4
|
+
findRelationshipDefinition,
|
|
5
|
+
getVisibleRelationshipParent,
|
|
6
|
+
handleWriteMethodError,
|
|
7
|
+
validateRelationshipRoutePayload
|
|
8
|
+
} from './common.js'
|
|
3
9
|
import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js'
|
|
10
|
+
import {
|
|
11
|
+
normalizeRelationshipIdentifiers,
|
|
12
|
+
requireExistingResourceId
|
|
13
|
+
} from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* POST RELATIONSHIP
|
|
@@ -12,9 +22,13 @@ import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js
|
|
|
12
22
|
* @param {array} relationshipData - Array of resource identifiers to add
|
|
13
23
|
* @returns {Promise<void>} 204 No Content
|
|
14
24
|
*/
|
|
15
|
-
export default async function postRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
|
|
25
|
+
export default async function postRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
16
26
|
context.method = 'postRelationship'
|
|
17
|
-
context.id = params.id
|
|
27
|
+
context.id = requireExistingResourceId(params.id, {
|
|
28
|
+
scopeOptions,
|
|
29
|
+
vars,
|
|
30
|
+
scopeName
|
|
31
|
+
})
|
|
18
32
|
context.relationshipName = params.relationshipName
|
|
19
33
|
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
20
34
|
|
|
@@ -42,21 +56,28 @@ export default async function postRelationshipMethod ({ params, context, vars, h
|
|
|
42
56
|
)
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
params.relationshipData = validateRelationshipRoutePayload({
|
|
60
|
+
context,
|
|
61
|
+
vars,
|
|
62
|
+
scopeName,
|
|
63
|
+
operation: 'postRelationship',
|
|
64
|
+
relationshipData: params.relationshipData
|
|
65
|
+
})
|
|
66
|
+
params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
|
|
48
67
|
|
|
49
68
|
// Check permissions
|
|
50
69
|
await runHooks('checkPermissions')
|
|
51
70
|
await runHooks('checkPermissionsPostRelationship')
|
|
52
71
|
|
|
53
|
-
// Verify parent exists
|
|
54
|
-
const
|
|
72
|
+
// Verify the parent exists in the caller's visible dataset.
|
|
73
|
+
const parentRecord = await getVisibleRelationshipParent({
|
|
74
|
+
context,
|
|
75
|
+
helpers,
|
|
55
76
|
scopeName,
|
|
56
|
-
|
|
77
|
+
runHooks
|
|
57
78
|
})
|
|
58
79
|
|
|
59
|
-
if (!
|
|
80
|
+
if (!parentRecord) {
|
|
60
81
|
throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
61
82
|
}
|
|
62
83
|
|
|
@@ -75,7 +96,7 @@ export default async function postRelationshipMethod ({ params, context, vars, h
|
|
|
75
96
|
through: relDef.through,
|
|
76
97
|
foreignKey: relDef.foreignKey,
|
|
77
98
|
otherKey: relDef.otherKey
|
|
78
|
-
}, params.relationshipData, context.transaction)
|
|
99
|
+
}, params.relationshipData, context.transaction, context)
|
|
79
100
|
}
|
|
80
101
|
} else if (relDef.type === 'hasMany') {
|
|
81
102
|
// Update foreign keys for hasMany
|
|
@@ -92,16 +113,14 @@ export default async function postRelationshipMethod ({ params, context, vars, h
|
|
|
92
113
|
},
|
|
93
114
|
transaction: context.transaction,
|
|
94
115
|
simplified: false
|
|
95
|
-
})
|
|
116
|
+
}, { ...context })
|
|
96
117
|
}
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
await runHooks('finish')
|
|
100
121
|
await runHooks('finishPostRelationship')
|
|
101
122
|
|
|
102
|
-
|
|
103
|
-
await context.transaction.commit()
|
|
104
|
-
}
|
|
123
|
+
await commitOwnedTransaction(context, runHooks)
|
|
105
124
|
|
|
106
125
|
// 204 No Content
|
|
107
126
|
} catch (error) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { processRelationships } from '../lib/writing/relationship-processor.js'
|
|
2
2
|
import { getRequestContracts, validateRequestContractOrThrow } from '../lib/querying-writing/request-contracts.js'
|
|
3
3
|
import { createPivotRecords } from '../lib/writing/many-to-many-manipulations.js'
|
|
4
|
+
import { requireDocumentResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
5
|
import {
|
|
5
6
|
setupCommonRequest,
|
|
6
7
|
validateResourceAttributesBeforeWrite,
|
|
@@ -8,7 +9,8 @@ import {
|
|
|
8
9
|
applyFieldSetters,
|
|
9
10
|
validatePivotResource,
|
|
10
11
|
handleRecordReturnAfterWrite,
|
|
11
|
-
handleWriteMethodError
|
|
12
|
+
handleWriteMethodError,
|
|
13
|
+
commitOwnedTransaction
|
|
12
14
|
} from './common.js'
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -62,6 +64,13 @@ export default async function postMethod ({
|
|
|
62
64
|
'POST request body is invalid'
|
|
63
65
|
)
|
|
64
66
|
|
|
67
|
+
if (context.inputRecord?.data?.id !== undefined) {
|
|
68
|
+
context.inputRecord.data.id = requireDocumentResourceId(context.inputRecord.data.id, {
|
|
69
|
+
scopeOptions,
|
|
70
|
+
vars
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
// Validate that user has read access to all related resources
|
|
66
75
|
// This ensures users can only create relationships to resources they can access
|
|
67
76
|
await validateRelationshipAccess(context, context.inputRecord, helpers, api)
|
|
@@ -143,7 +152,7 @@ export default async function postMethod ({
|
|
|
143
152
|
|
|
144
153
|
// Validate pivot resource exists
|
|
145
154
|
validatePivotResource(scopes, relDef, relName)
|
|
146
|
-
await createPivotRecords(api, context.id, relDef, relData, context.transaction)
|
|
155
|
+
await createPivotRecords(api, context.id, relDef, relData, context.transaction, context)
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
const ret = await handleRecordReturnAfterWrite({
|
|
@@ -160,11 +169,7 @@ export default async function postMethod ({
|
|
|
160
169
|
log
|
|
161
170
|
})
|
|
162
171
|
|
|
163
|
-
|
|
164
|
-
if (context.shouldCommit) {
|
|
165
|
-
await context.transaction.commit()
|
|
166
|
-
await runHooks('afterCommit')
|
|
167
|
-
}
|
|
172
|
+
await commitOwnedTransaction(context, runHooks)
|
|
168
173
|
|
|
169
174
|
return ret
|
|
170
175
|
} catch (error) {
|