json-rest-api 1.0.13 → 1.0.15
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/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
import { validateGetPayload } from '../lib/querying-writing/payload-validators.js'
|
|
3
|
-
import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
|
|
4
|
-
import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
|
|
5
|
-
import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
|
|
6
|
-
import { cascadeConfig } from './common.js'
|
|
1
|
+
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
import { validateGetPayload } from '../lib/querying-writing/payload-validators.js'
|
|
3
|
+
import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
|
|
4
|
+
import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
|
|
5
|
+
import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
|
|
6
|
+
import { cascadeConfig } from './common.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* GET
|
|
@@ -14,171 +14,171 @@ import { cascadeConfig } from './common.js';
|
|
|
14
14
|
* @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets). Keys are resource types, values are comma-separated field names.
|
|
15
15
|
* @returns {Promise<object>} A Promise that resolves to the JSON:API response document.
|
|
16
16
|
*/
|
|
17
|
-
export default async function getMethod({
|
|
18
|
-
params,
|
|
19
|
-
context,
|
|
20
|
-
vars,
|
|
21
|
-
helpers,
|
|
22
|
-
scope,
|
|
23
|
-
scopes,
|
|
24
|
-
runHooks,
|
|
25
|
-
apiOptions,
|
|
26
|
-
pluginOptions,
|
|
27
|
-
scopeOptions,
|
|
17
|
+
export default async function getMethod ({
|
|
18
|
+
params,
|
|
19
|
+
context,
|
|
20
|
+
vars,
|
|
21
|
+
helpers,
|
|
22
|
+
scope,
|
|
23
|
+
scopes,
|
|
24
|
+
runHooks,
|
|
25
|
+
apiOptions,
|
|
26
|
+
pluginOptions,
|
|
27
|
+
scopeOptions,
|
|
28
28
|
scopeName,
|
|
29
29
|
api
|
|
30
30
|
}) {
|
|
31
|
-
context.method = 'get'
|
|
31
|
+
context.method = 'get'
|
|
32
32
|
|
|
33
33
|
// Determine which simplified setting to use based on transport
|
|
34
|
-
const isTransport = params.isTransport === true
|
|
35
|
-
|
|
34
|
+
const isTransport = params.isTransport === true
|
|
35
|
+
|
|
36
36
|
// Use vars which automatically cascade from scope to global
|
|
37
|
-
const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
|
|
38
|
-
|
|
37
|
+
const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
|
|
38
|
+
|
|
39
39
|
// Get simplified setting - from params only (per-call override) or use default
|
|
40
|
-
context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
|
|
40
|
+
context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
|
|
41
41
|
|
|
42
42
|
// Assign common context properties
|
|
43
|
-
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
44
|
-
context.queryParams = params.queryParams || {}
|
|
43
|
+
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
44
|
+
context.queryParams = params.queryParams || {}
|
|
45
45
|
|
|
46
46
|
// These only make sense as parameter per query
|
|
47
|
-
context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
|
|
48
|
-
context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
|
|
49
|
-
|
|
50
|
-
context.transaction = params.transaction;
|
|
51
|
-
context.db = context.transaction || api.knex.instance;
|
|
47
|
+
context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
|
|
48
|
+
context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
|
|
52
49
|
|
|
53
|
-
context.
|
|
50
|
+
context.transaction = params.transaction
|
|
51
|
+
context.db = context.transaction || api.knex.instance
|
|
52
|
+
|
|
53
|
+
context.scopeName = scopeName
|
|
54
54
|
|
|
55
55
|
// These are just shortcuts used in this function and will be returned
|
|
56
|
-
const schemaStructure = context.schemaInfo.schemaInstance.structure
|
|
57
|
-
const schemaRelationships = context.schemaInfo.schemaRelationships
|
|
56
|
+
const schemaStructure = context.schemaInfo.schemaInstance.structure
|
|
57
|
+
const schemaRelationships = context.schemaInfo.schemaRelationships
|
|
58
58
|
|
|
59
|
-
context.id = params.id
|
|
59
|
+
context.id = params.id
|
|
60
60
|
|
|
61
61
|
// Validate GET request to ensure required parameters are present and properly formatted.
|
|
62
62
|
// This checks that 'id' parameter exists and is not empty (you can't GET without an ID),
|
|
63
63
|
// validates 'include' contains valid relationship names (not arbitrary fields), and ensures
|
|
64
64
|
// 'fields' for sparse fieldsets follow the format fields[type]=comma,separated,list.
|
|
65
65
|
// Example: validates id: '123' exists, include: ['author', 'tags'] are real relationships.
|
|
66
|
-
validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
|
|
66
|
+
validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
|
|
67
67
|
|
|
68
68
|
// Fetch minimal record for authorization checks
|
|
69
69
|
const minimalRecord = await helpers.dataGetMinimal({
|
|
70
70
|
scopeName,
|
|
71
71
|
context,
|
|
72
72
|
runHooks
|
|
73
|
-
})
|
|
73
|
+
})
|
|
74
74
|
|
|
75
75
|
if (!minimalRecord) {
|
|
76
76
|
throw new RestApiResourceError(
|
|
77
|
-
|
|
78
|
-
{
|
|
77
|
+
'Resource not found',
|
|
78
|
+
{
|
|
79
79
|
subtype: 'not_found',
|
|
80
80
|
resourceType: scopeName,
|
|
81
81
|
resourceId: context.id
|
|
82
82
|
}
|
|
83
|
-
)
|
|
83
|
+
)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
context.minimalRecord = minimalRecord
|
|
86
|
+
context.minimalRecord = minimalRecord
|
|
87
87
|
|
|
88
88
|
// Centralised checkPermissions function
|
|
89
89
|
await scope.checkPermissions({
|
|
90
90
|
method: 'get',
|
|
91
91
|
originalContext: context,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
await runHooks('beforeData')
|
|
95
|
-
await runHooks('beforeDataGet')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await runHooks('beforeData')
|
|
95
|
+
await runHooks('beforeDataGet')
|
|
96
96
|
|
|
97
97
|
context.record = await helpers.dataGet({
|
|
98
98
|
scopeName,
|
|
99
99
|
context,
|
|
100
100
|
runHooks
|
|
101
|
-
})
|
|
101
|
+
})
|
|
102
102
|
|
|
103
103
|
// Check if record was found - storage layer returns null/undefined for non-existent records.
|
|
104
104
|
// This generates a proper 404 error with JSON:API error format instead of returning empty data.
|
|
105
105
|
if (!context.record || !context.record.data) {
|
|
106
106
|
throw new RestApiResourceError(
|
|
107
|
-
|
|
108
|
-
{
|
|
107
|
+
'Resource not found',
|
|
108
|
+
{
|
|
109
109
|
subtype: 'not_found',
|
|
110
110
|
resourceType: scopeName,
|
|
111
111
|
resourceId: context.id
|
|
112
112
|
}
|
|
113
|
-
)
|
|
113
|
+
)
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Normalize database values (e.g., convert 1/0 to true/false for booleans)
|
|
117
|
-
context.record = normalizeRecordAttributes(context.record, scopes)
|
|
117
|
+
context.record = normalizeRecordAttributes(context.record, scopes)
|
|
118
|
+
|
|
119
|
+
await runHooks('checkDataPermissions')
|
|
120
|
+
await runHooks('checkDataPermissionsGet')
|
|
118
121
|
|
|
119
|
-
await runHooks('checkDataPermissions');
|
|
120
|
-
await runHooks('checkDataPermissionsGet');
|
|
121
|
-
|
|
122
122
|
// Make a backup
|
|
123
|
-
context.originalRecord = structuredClone(context.record)
|
|
123
|
+
context.originalRecord = structuredClone(context.record)
|
|
124
124
|
|
|
125
125
|
// This will enhance record, which is the WHOLE JSON:API record
|
|
126
|
-
await runHooks('enrichRecord')
|
|
127
|
-
|
|
126
|
+
await runHooks('enrichRecord')
|
|
127
|
+
|
|
128
128
|
// Get computed field information for main resource
|
|
129
|
-
const computedFields = scope.vars.schemaInfo?.computed || {}
|
|
130
|
-
const requestedFields = context.queryParams.fields?.[scopeName]
|
|
131
|
-
const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
|
|
132
|
-
|
|
129
|
+
const computedFields = scope.vars.schemaInfo?.computed || {}
|
|
130
|
+
const requestedFields = context.queryParams.fields?.[scopeName]
|
|
131
|
+
const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
|
|
132
|
+
|
|
133
133
|
// Enrich attributes for the main resource
|
|
134
134
|
// Pass computedDependencies from context (set by dataGet in Knex plugin)
|
|
135
135
|
// This tells enrichAttributes which fields to remove after computation
|
|
136
|
-
context.record.data.attributes = await scope.enrichAttributes({
|
|
137
|
-
attributes: context.record.data.attributes,
|
|
136
|
+
context.record.data.attributes = await scope.enrichAttributes({
|
|
137
|
+
attributes: context.record.data.attributes,
|
|
138
138
|
parentContext: context,
|
|
139
|
-
requestedComputedFields
|
|
139
|
+
requestedComputedFields,
|
|
140
140
|
isMainResource: true,
|
|
141
141
|
computedDependencies: context.computedDependencies // Fields to remove if not requested
|
|
142
|
-
})
|
|
143
|
-
|
|
142
|
+
})
|
|
143
|
+
|
|
144
144
|
for (const entry of (context.record.included || [])) {
|
|
145
|
-
const entryScope = scopes[entry.type]
|
|
146
|
-
const entryComputed = entryScope.vars.schemaInfo?.computed || {}
|
|
147
|
-
const entryRequestedFields = context.queryParams.fields?.[entry.type]
|
|
145
|
+
const entryScope = scopes[entry.type]
|
|
146
|
+
const entryComputed = entryScope.vars.schemaInfo?.computed || {}
|
|
147
|
+
const entryRequestedFields = context.queryParams.fields?.[entry.type]
|
|
148
148
|
const entryRequestedComputed = getRequestedComputedFields(
|
|
149
|
-
entry.type,
|
|
150
|
-
entryRequestedFields,
|
|
149
|
+
entry.type,
|
|
150
|
+
entryRequestedFields,
|
|
151
151
|
entryComputed
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
entry.attributes = await entryScope.enrichAttributes({
|
|
155
|
-
attributes: entry.attributes,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
entry.attributes = await entryScope.enrichAttributes({
|
|
155
|
+
attributes: entry.attributes,
|
|
156
156
|
parentContext: context,
|
|
157
157
|
requestedComputedFields: entryRequestedComputed,
|
|
158
158
|
isMainResource: false,
|
|
159
159
|
computedDependencies: entry.__$jsonrestapi_computed_deps$__
|
|
160
|
-
})
|
|
160
|
+
})
|
|
161
161
|
}
|
|
162
|
-
|
|
163
|
-
await runHooks('enrichRecordWithRelationships')
|
|
162
|
+
|
|
163
|
+
await runHooks('enrichRecordWithRelationships')
|
|
164
164
|
|
|
165
165
|
// The called hooks should NOT change context.record
|
|
166
|
-
await runHooks('finish')
|
|
167
|
-
await runHooks('finishGet')
|
|
168
|
-
|
|
166
|
+
await runHooks('finish')
|
|
167
|
+
await runHooks('finishGet')
|
|
168
|
+
|
|
169
169
|
// Get schema info for transformation
|
|
170
|
-
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
171
|
-
|
|
170
|
+
context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
171
|
+
|
|
172
172
|
// Transform output if in simplified mode
|
|
173
173
|
if (context.simplified) {
|
|
174
174
|
// Convert JSON:API response back to simplified format
|
|
175
|
-
// Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
|
|
175
|
+
// Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
|
|
176
176
|
// becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
|
|
177
177
|
return transformJsonApiToSimplified(
|
|
178
178
|
{ record: context.record },
|
|
179
179
|
{ context: { schemaStructure, schemaRelationships, scopes } }
|
|
180
|
-
)
|
|
180
|
+
)
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
return context.record
|
|
184
|
-
}
|
|
182
|
+
|
|
183
|
+
return context.record
|
|
184
|
+
}
|
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
* PATCH RELATIONSHIP
|
|
3
3
|
* Completely replaces a relationship
|
|
4
4
|
* PATCH /api/articles/1/relationships/author
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @param {string} id - The ID of the resource
|
|
7
7
|
* @param {string} relationshipName - The name of the relationship
|
|
8
8
|
* @param {object|array} relationshipData - New relationship data
|
|
9
9
|
* @returns {Promise<void>} 204 No Content
|
|
10
10
|
*/
|
|
11
11
|
export default async function patchRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api }) {
|
|
12
|
-
context.method = 'patchRelationship'
|
|
13
|
-
context.id = params.id
|
|
14
|
-
context.relationshipName = params.relationshipName
|
|
12
|
+
context.method = 'patchRelationship'
|
|
13
|
+
context.id = params.id
|
|
14
|
+
context.relationshipName = params.relationshipName
|
|
15
15
|
|
|
16
16
|
// Transaction handling
|
|
17
|
-
context.transaction = params.transaction ||
|
|
18
|
-
(helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
19
|
-
context.shouldCommit = !params.transaction && !!context.transaction
|
|
17
|
+
context.transaction = params.transaction ||
|
|
18
|
+
(helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
19
|
+
context.shouldCommit = !params.transaction && !!context.transaction
|
|
20
20
|
context.db = context.transaction || api.knex.instance
|
|
21
21
|
|
|
22
22
|
try {
|
|
23
23
|
// Check permissions
|
|
24
|
-
await runHooks('checkPermissions')
|
|
25
|
-
await runHooks('checkPermissionsPatchRelationship')
|
|
24
|
+
await runHooks('checkPermissions')
|
|
25
|
+
await runHooks('checkPermissionsPatchRelationship')
|
|
26
26
|
|
|
27
27
|
// Reuse existing patch with relationship data
|
|
28
28
|
await scope.patch({
|
|
@@ -39,18 +39,17 @@ export default async function patchRelationshipMethod ({ params, context, vars,
|
|
|
39
39
|
transaction: context.transaction,
|
|
40
40
|
simplified: false,
|
|
41
41
|
isTransport: params.isTransport
|
|
42
|
-
})
|
|
42
|
+
})
|
|
43
43
|
|
|
44
|
-
await runHooks('finish')
|
|
45
|
-
await runHooks('finishPatchRelationship')
|
|
44
|
+
await runHooks('finish')
|
|
45
|
+
await runHooks('finishPatchRelationship')
|
|
46
46
|
|
|
47
47
|
if (context.shouldCommit) {
|
|
48
|
-
await context.transaction.commit()
|
|
48
|
+
await context.transaction.commit()
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// 204 No Content
|
|
53
52
|
} catch (error) {
|
|
54
|
-
await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
|
|
53
|
+
await handleWriteMethodError(error, context, 'PATCH_RELATIONSHIP', scopeName, log)
|
|
55
54
|
}
|
|
56
|
-
}
|
|
55
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
import { validatePatchPayload } from '../lib/querying-writing/payload-validators.js'
|
|
3
|
-
import { processRelationships } from '../lib/writing/relationship-processor.js'
|
|
4
|
-
import { updateManyToManyRelationship } from '../lib/writing/many-to-many-manipulations.js'
|
|
5
|
-
import { ERROR_SUBTYPES } from '../lib/querying-writing/knex-constants.js'
|
|
6
|
-
import {
|
|
7
|
-
setupCommonRequest,
|
|
1
|
+
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
import { validatePatchPayload } from '../lib/querying-writing/payload-validators.js'
|
|
3
|
+
import { processRelationships } from '../lib/writing/relationship-processor.js'
|
|
4
|
+
import { updateManyToManyRelationship } from '../lib/writing/many-to-many-manipulations.js'
|
|
5
|
+
import { ERROR_SUBTYPES } from '../lib/querying-writing/knex-constants.js'
|
|
6
|
+
import {
|
|
7
|
+
setupCommonRequest,
|
|
8
8
|
validateResourceAttributesBeforeWrite,
|
|
9
9
|
validateRelationshipAccess,
|
|
10
10
|
applyFieldSetters,
|
|
11
11
|
validatePivotResource,
|
|
12
12
|
handleRecordReturnAfterWrite,
|
|
13
13
|
handleWriteMethodError
|
|
14
|
-
} from './common.js'
|
|
14
|
+
} from './common.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* PATCH
|
|
@@ -21,39 +21,39 @@ import {
|
|
|
21
21
|
* For relationships, only the ones explicitly provided will be updated.
|
|
22
22
|
* Just like PUT, it CANNOT have the `included` array in data.
|
|
23
23
|
*/
|
|
24
|
-
export default async function patchMethod({
|
|
25
|
-
params,
|
|
26
|
-
context,
|
|
27
|
-
vars,
|
|
28
|
-
helpers,
|
|
29
|
-
scope,
|
|
30
|
-
scopes,
|
|
31
|
-
runHooks,
|
|
32
|
-
apiOptions,
|
|
33
|
-
pluginOptions,
|
|
34
|
-
scopeOptions,
|
|
24
|
+
export default async function patchMethod ({
|
|
25
|
+
params,
|
|
26
|
+
context,
|
|
27
|
+
vars,
|
|
28
|
+
helpers,
|
|
29
|
+
scope,
|
|
30
|
+
scopes,
|
|
31
|
+
runHooks,
|
|
32
|
+
apiOptions,
|
|
33
|
+
pluginOptions,
|
|
34
|
+
scopeOptions,
|
|
35
35
|
scopeName,
|
|
36
36
|
api,
|
|
37
37
|
log
|
|
38
38
|
}) {
|
|
39
|
-
context.method = 'patch'
|
|
40
|
-
|
|
39
|
+
context.method = 'patch'
|
|
40
|
+
|
|
41
41
|
try {
|
|
42
42
|
const { schema, schemaStructure, schemaRelationships } = await setupCommonRequest({
|
|
43
|
-
params,
|
|
44
|
-
context,
|
|
45
|
-
vars,
|
|
46
|
-
scopes,
|
|
47
|
-
scopeOptions,
|
|
43
|
+
params,
|
|
44
|
+
context,
|
|
45
|
+
vars,
|
|
46
|
+
scopes,
|
|
47
|
+
scopeOptions,
|
|
48
48
|
scopeName,
|
|
49
|
-
api,
|
|
49
|
+
api,
|
|
50
50
|
helpers
|
|
51
|
-
})
|
|
52
|
-
context.id = context.inputRecord.data.id
|
|
53
|
-
|
|
51
|
+
})
|
|
52
|
+
context.id = context.inputRecord.data.id
|
|
53
|
+
|
|
54
54
|
// Run early hooks for pre-processing (e.g., file handling)
|
|
55
|
-
await runHooks('beforeProcessing')
|
|
56
|
-
await runHooks('beforeProcessingPatch')
|
|
55
|
+
await runHooks('beforeProcessing')
|
|
56
|
+
await runHooks('beforeProcessingPatch')
|
|
57
57
|
|
|
58
58
|
// Validate PATCH payload to ensure the partial update actually contains changes.
|
|
59
59
|
// PATCH requests must include either attributes to update or relationships to modify -
|
|
@@ -61,11 +61,11 @@ export default async function patchMethod({
|
|
|
61
61
|
// are explicit about what they want to change. Unlike PUT, PATCH preserves all fields
|
|
62
62
|
// not mentioned in the request.
|
|
63
63
|
// Example: data must have either attributes: {title: 'New'} or relationships: {author: {...}}
|
|
64
|
-
validatePatchPayload(context.inputRecord, scopes)
|
|
65
|
-
|
|
64
|
+
validatePatchPayload(context.inputRecord, scopes)
|
|
65
|
+
|
|
66
66
|
// Validate that user has read access to all related resources
|
|
67
67
|
// This ensures users can only create relationships to resources they can access
|
|
68
|
-
await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api)
|
|
68
|
+
await validateRelationshipAccess(context, context.inputRecord, helpers, runHooks, api)
|
|
69
69
|
|
|
70
70
|
// Extract foreign keys from JSON:API relationships and prepare many-to-many operations
|
|
71
71
|
// Example: relationships.author -> author_id: '123' for storage
|
|
@@ -73,48 +73,48 @@ export default async function patchMethod({
|
|
|
73
73
|
const { belongsToUpdates, manyToManyRelationships } = processRelationships(
|
|
74
74
|
scope,
|
|
75
75
|
{ context }
|
|
76
|
-
)
|
|
76
|
+
)
|
|
77
77
|
|
|
78
|
-
await validateResourceAttributesBeforeWrite({
|
|
79
|
-
context,
|
|
80
|
-
schema,
|
|
81
|
-
belongsToUpdates,
|
|
78
|
+
await validateResourceAttributesBeforeWrite({
|
|
79
|
+
context,
|
|
80
|
+
schema,
|
|
81
|
+
belongsToUpdates,
|
|
82
82
|
runHooks,
|
|
83
|
-
isPartialValidation: true
|
|
84
|
-
})
|
|
83
|
+
isPartialValidation: true
|
|
84
|
+
})
|
|
85
85
|
|
|
86
86
|
// Fetch minimal record for authorization checks
|
|
87
87
|
const minimalRecord = await helpers.dataGetMinimal({
|
|
88
88
|
scopeName,
|
|
89
89
|
context,
|
|
90
90
|
runHooks
|
|
91
|
-
})
|
|
91
|
+
})
|
|
92
92
|
|
|
93
93
|
if (!minimalRecord) {
|
|
94
94
|
throw new RestApiResourceError(
|
|
95
95
|
`Resource not found: ${scopeName}/${context.id}`,
|
|
96
96
|
ERROR_SUBTYPES.NOT_FOUND
|
|
97
|
-
)
|
|
97
|
+
)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
context.minimalRecord = minimalRecord
|
|
100
|
+
context.minimalRecord = minimalRecord
|
|
101
101
|
|
|
102
102
|
// Centralised checkPermissions function
|
|
103
103
|
await scope.checkPermissions({
|
|
104
104
|
method: 'patch',
|
|
105
105
|
originalContext: context,
|
|
106
|
-
})
|
|
106
|
+
})
|
|
107
107
|
|
|
108
108
|
// Merge belongsTo updates into attributes before patching the record
|
|
109
109
|
if (Object.keys(belongsToUpdates).length > 0) {
|
|
110
110
|
context.inputRecord.data.attributes = {
|
|
111
111
|
...context.inputRecord.data.attributes,
|
|
112
112
|
...belongsToUpdates
|
|
113
|
-
}
|
|
113
|
+
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
await runHooks('beforeDataCall')
|
|
117
|
-
await runHooks('beforeDataCallPatch')
|
|
116
|
+
await runHooks('beforeDataCall')
|
|
117
|
+
await runHooks('beforeDataCallPatch')
|
|
118
118
|
|
|
119
119
|
// Apply field setters after validation and before storage
|
|
120
120
|
if (context.inputRecord?.data?.attributes) {
|
|
@@ -124,17 +124,17 @@ export default async function patchMethod({
|
|
|
124
124
|
context,
|
|
125
125
|
api,
|
|
126
126
|
helpers
|
|
127
|
-
)
|
|
127
|
+
)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// Call the storage helper - should return the patched record
|
|
131
131
|
await helpers.dataPatch({
|
|
132
132
|
scopeName,
|
|
133
133
|
context
|
|
134
|
-
})
|
|
134
|
+
})
|
|
135
135
|
|
|
136
|
-
await runHooks('afterDataCallPatch')
|
|
137
|
-
await runHooks('afterDataCall')
|
|
136
|
+
await runHooks('afterDataCallPatch')
|
|
137
|
+
await runHooks('afterDataCall')
|
|
138
138
|
|
|
139
139
|
// Process many-to-many relationships after main record update
|
|
140
140
|
// For PATCH, we only update the relationships that were explicitly provided
|
|
@@ -147,11 +147,11 @@ export default async function patchMethod({
|
|
|
147
147
|
relDef,
|
|
148
148
|
relData,
|
|
149
149
|
isUpdate: true,
|
|
150
|
-
})
|
|
151
|
-
continue
|
|
150
|
+
})
|
|
151
|
+
continue
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
validatePivotResource(scopes, relDef, relName)
|
|
154
|
+
validatePivotResource(scopes, relDef, relName)
|
|
155
155
|
|
|
156
156
|
await updateManyToManyRelationship(null, {
|
|
157
157
|
api,
|
|
@@ -161,9 +161,9 @@ export default async function patchMethod({
|
|
|
161
161
|
relData,
|
|
162
162
|
transaction: context.transaction
|
|
163
163
|
}
|
|
164
|
-
})
|
|
164
|
+
})
|
|
165
165
|
}
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
const ret = await handleRecordReturnAfterWrite({
|
|
168
168
|
context,
|
|
169
169
|
scopeName,
|
|
@@ -176,17 +176,16 @@ export default async function patchMethod({
|
|
|
176
176
|
runHooks,
|
|
177
177
|
helpers,
|
|
178
178
|
log
|
|
179
|
-
})
|
|
179
|
+
})
|
|
180
180
|
|
|
181
181
|
// Commit transaction if we created it
|
|
182
182
|
if (context.shouldCommit) {
|
|
183
|
-
await context.transaction.commit()
|
|
184
|
-
await runHooks('afterCommit')
|
|
183
|
+
await context.transaction.commit()
|
|
184
|
+
await runHooks('afterCommit')
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
return ret
|
|
188
|
-
|
|
187
|
+
return ret
|
|
189
188
|
} catch (error) {
|
|
190
|
-
await handleWriteMethodError(error, context, 'PATCH', scopeName, log, runHooks)
|
|
189
|
+
await handleWriteMethodError(error, context, 'PATCH', scopeName, log, runHooks)
|
|
191
190
|
}
|
|
192
191
|
}
|