json-rest-api 1.0.14 → 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 +11 -2
- 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 +1 -1
- 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,75 +1,75 @@
|
|
|
1
|
-
import { Api } from 'hooked-api'
|
|
2
|
-
import { RestApiPlugin, RestApiKnexPlugin, RestApiAnyapiKnexPlugin } from '../../index.js'
|
|
3
|
-
import { AccessPlugin } from '../../plugins/core/rest-api-access.js'
|
|
4
|
-
import { ExpressPlugin } from '../../plugins/core/connectors/express-plugin.js'
|
|
5
|
-
import express from 'express'
|
|
6
|
-
import { createServer } from 'http'
|
|
7
|
-
import { ensureAnyApiSchema } from '../../plugins/core/lib/anyapi/schema-utils.js'
|
|
8
|
-
import { storageMode } from '../helpers/storage-mode.js'
|
|
9
|
-
|
|
10
|
-
async function resetAnyApiTables(knex) {
|
|
11
|
-
if (!storageMode.isAnyApi()) return
|
|
12
|
-
await knex('any_links').delete().catch(() => {})
|
|
13
|
-
await knex('any_records').delete().catch(() => {})
|
|
14
|
-
await knex('any_relationship_configs').delete().catch(() => {})
|
|
15
|
-
await knex('any_field_configs').delete().catch(() => {})
|
|
16
|
-
await knex('any_resource_configs').delete().catch(() => {})
|
|
1
|
+
import { Api } from 'hooked-api'
|
|
2
|
+
import { RestApiPlugin, RestApiKnexPlugin, RestApiAnyapiKnexPlugin } from '../../index.js'
|
|
3
|
+
import { AccessPlugin } from '../../plugins/core/rest-api-access.js'
|
|
4
|
+
import { ExpressPlugin } from '../../plugins/core/connectors/express-plugin.js'
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import { createServer } from 'http'
|
|
7
|
+
import { ensureAnyApiSchema } from '../../plugins/core/lib/anyapi/schema-utils.js'
|
|
8
|
+
import { storageMode } from '../helpers/storage-mode.js'
|
|
9
|
+
|
|
10
|
+
async function resetAnyApiTables (knex) {
|
|
11
|
+
if (!storageMode.isAnyApi()) return
|
|
12
|
+
await knex('any_links').delete().catch(() => {})
|
|
13
|
+
await knex('any_records').delete().catch(() => {})
|
|
14
|
+
await knex('any_relationship_configs').delete().catch(() => {})
|
|
15
|
+
await knex('any_field_configs').delete().catch(() => {})
|
|
16
|
+
await knex('any_resource_configs').delete().catch(() => {})
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async function useStoragePlugin(api, knex, { tenantId } = {}) {
|
|
19
|
+
async function useStoragePlugin (api, knex, { tenantId } = {}) {
|
|
20
20
|
if (storageMode.isAnyApi()) {
|
|
21
|
-
await ensureAnyApiSchema(knex)
|
|
22
|
-
const sanitizedName = api?.name ? api.name.replace(/[^A-Za-z0-9]+/g, '_').toLowerCase() : 'tenant'
|
|
23
|
-
const effectiveTenantId = tenantId || api?.anyapi?.tenantId || `${sanitizedName}_tenant
|
|
24
|
-
api.anyapi = api.anyapi || {}
|
|
25
|
-
api.anyapi.tenantId = effectiveTenantId
|
|
26
|
-
await api.use(RestApiAnyapiKnexPlugin, { knex, tenantId: effectiveTenantId })
|
|
27
|
-
storageMode.setCurrentTenant(effectiveTenantId)
|
|
21
|
+
await ensureAnyApiSchema(knex)
|
|
22
|
+
const sanitizedName = api?.name ? api.name.replace(/[^A-Za-z0-9]+/g, '_').toLowerCase() : 'tenant'
|
|
23
|
+
const effectiveTenantId = tenantId || api?.anyapi?.tenantId || `${sanitizedName}_tenant`
|
|
24
|
+
api.anyapi = api.anyapi || {}
|
|
25
|
+
api.anyapi.tenantId = effectiveTenantId
|
|
26
|
+
await api.use(RestApiAnyapiKnexPlugin, { knex, tenantId: effectiveTenantId })
|
|
27
|
+
storageMode.setCurrentTenant(effectiveTenantId)
|
|
28
28
|
} else {
|
|
29
|
-
await api.use(RestApiKnexPlugin, { knex })
|
|
29
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function mapTable(tableName, resourceName) {
|
|
33
|
+
function mapTable (tableName, resourceName) {
|
|
34
34
|
if (storageMode.isAnyApi()) {
|
|
35
|
-
storageMode.registerTable(tableName, resourceName)
|
|
35
|
+
storageMode.registerTable(tableName, resourceName)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
async function withTenantContext(tenantId, fn) {
|
|
39
|
+
async function withTenantContext (tenantId, fn) {
|
|
40
40
|
if (!storageMode.isAnyApi()) {
|
|
41
|
-
return await fn()
|
|
41
|
+
return await fn()
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const previousTenant = storageMode.currentTenant
|
|
45
|
-
storageMode.setCurrentTenant(tenantId || storageMode.defaultTenant)
|
|
44
|
+
const previousTenant = storageMode.currentTenant
|
|
45
|
+
storageMode.setCurrentTenant(tenantId || storageMode.defaultTenant)
|
|
46
46
|
try {
|
|
47
|
-
return await fn()
|
|
47
|
+
return await fn()
|
|
48
48
|
} finally {
|
|
49
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
49
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Creates a basic API configuration with Countries, Publishers, Authors, Books
|
|
55
55
|
*/
|
|
56
|
-
export async function createBasicApi(knex, pluginOptions = {}) {
|
|
57
|
-
const apiName = pluginOptions.apiName ||
|
|
58
|
-
const tablePrefix = pluginOptions.tablePrefix || 'basic'
|
|
56
|
+
export async function createBasicApi (knex, pluginOptions = {}) {
|
|
57
|
+
const apiName = pluginOptions.apiName || 'basic-test-api'
|
|
58
|
+
const tablePrefix = pluginOptions.tablePrefix || 'basic'
|
|
59
59
|
if (storageMode.isAnyApi()) {
|
|
60
|
-
storageMode.clearRegistry()
|
|
60
|
+
storageMode.clearRegistry()
|
|
61
61
|
}
|
|
62
62
|
const api = new Api({
|
|
63
63
|
name: apiName,
|
|
64
64
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
65
|
-
})
|
|
65
|
+
})
|
|
66
66
|
|
|
67
|
-
const previousTenant = storageMode.currentTenant
|
|
67
|
+
const previousTenant = storageMode.currentTenant
|
|
68
68
|
const tenantId = storageMode.isAnyApi()
|
|
69
69
|
? (pluginOptions.tenantId || `${tablePrefix}_tenant`)
|
|
70
|
-
: storageMode.defaultTenant
|
|
70
|
+
: storageMode.defaultTenant
|
|
71
71
|
if (storageMode.isAnyApi()) {
|
|
72
|
-
storageMode.setCurrentTenant(tenantId)
|
|
72
|
+
storageMode.setCurrentTenant(tenantId)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const restApiOptions = {
|
|
@@ -87,134 +87,134 @@ export async function createBasicApi(knex, pluginOptions = {}) {
|
|
|
87
87
|
},
|
|
88
88
|
sortableFields: ['id', 'title', 'country_id', 'publisher_id', 'name', 'code'],
|
|
89
89
|
...pluginOptions['rest-api'] // Merge any custom options for rest-api plugin
|
|
90
|
-
}
|
|
90
|
+
}
|
|
91
91
|
|
|
92
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
93
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
94
|
-
await resetAnyApiTables(knex)
|
|
92
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
93
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
94
|
+
await resetAnyApiTables(knex)
|
|
95
95
|
|
|
96
96
|
// Add Express plugin if requested
|
|
97
97
|
if (pluginOptions.includeExpress) {
|
|
98
98
|
await api.use(ExpressPlugin, {
|
|
99
99
|
mountPath: '/api', // Default mount path for tests
|
|
100
100
|
...(pluginOptions.express || {})
|
|
101
|
-
})
|
|
101
|
+
})
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
105
|
// Countries table
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
await api.addResource('countries', {
|
|
107
|
+
schema: {
|
|
108
|
+
id: { type: 'id' },
|
|
109
|
+
name: { type: 'string', required: true, max: 100, search: true },
|
|
110
|
+
code: { type: 'string', max: 2, unique: true }
|
|
111
|
+
},
|
|
112
|
+
relationships: {
|
|
113
|
+
publishers: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
|
|
114
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'country_id' }
|
|
115
|
+
},
|
|
116
|
+
tableName: `${tablePrefix}_countries`
|
|
117
|
+
})
|
|
118
|
+
await api.resources.countries.createKnexTable()
|
|
119
|
+
mapTable(`${tablePrefix}_countries`, 'countries')
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
// Publishers table
|
|
122
|
+
await api.addResource('publishers', {
|
|
123
|
+
schema: {
|
|
124
|
+
id: { type: 'id' },
|
|
125
|
+
name: { type: 'string', required: true, max: 200 },
|
|
126
|
+
country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' }
|
|
127
|
+
},
|
|
128
|
+
relationships: {
|
|
129
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' }
|
|
130
|
+
},
|
|
131
|
+
tableName: `${tablePrefix}_publishers`
|
|
132
|
+
})
|
|
133
|
+
await api.resources.publishers.createKnexTable()
|
|
134
|
+
mapTable(`${tablePrefix}_publishers`, 'publishers')
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
// Authors table
|
|
137
|
+
await api.addResource('authors', {
|
|
138
|
+
schema: {
|
|
139
|
+
id: { type: 'id' },
|
|
140
|
+
name: { type: 'string', required: true, max: 200 }
|
|
141
|
+
},
|
|
142
|
+
relationships: {
|
|
143
|
+
books: { type: 'manyToMany', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' }
|
|
144
|
+
},
|
|
145
|
+
tableName: `${tablePrefix}_authors`
|
|
146
|
+
})
|
|
147
|
+
await api.resources.authors.createKnexTable()
|
|
148
|
+
mapTable(`${tablePrefix}_authors`, 'authors')
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
// Books table
|
|
151
|
+
await api.addResource('books', {
|
|
152
|
+
schema: {
|
|
153
|
+
id: { type: 'id' },
|
|
154
|
+
title: { type: 'string', required: true, max: 300, search: true },
|
|
155
|
+
country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country', search: true },
|
|
156
|
+
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher', search: true }
|
|
157
|
+
},
|
|
158
|
+
relationships: {
|
|
159
|
+
authors: { type: 'manyToMany', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' }
|
|
160
|
+
},
|
|
161
|
+
tableName: `${tablePrefix}_books`
|
|
162
|
+
})
|
|
163
|
+
await api.resources.books.createKnexTable()
|
|
164
|
+
mapTable(`${tablePrefix}_books`, 'books')
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
166
|
+
// Book-Authors pivot table
|
|
167
|
+
await api.addResource('book_authors', {
|
|
168
|
+
schema: {
|
|
169
|
+
id: { type: 'id' },
|
|
170
|
+
book_id: { type: 'number', required: true, belongsTo: 'books', as: 'book' },
|
|
171
|
+
author_id: { type: 'number', required: true, belongsTo: 'authors', as: 'author' }
|
|
172
|
+
},
|
|
173
|
+
tableName: `${tablePrefix}_book_authors`
|
|
174
|
+
})
|
|
175
|
+
await api.resources.book_authors.createKnexTable()
|
|
176
|
+
mapTable(`${tablePrefix}_book_authors`, 'book_authors')
|
|
177
|
+
if (storageMode.isAnyApi()) {
|
|
178
|
+
const descriptorTenant = api.anyapi?.tenantId || tenantId
|
|
179
|
+
const booksDescriptor = await api.anyapi.registry.getDescriptor(descriptorTenant, 'books')
|
|
180
|
+
const authorsDescriptor = await api.anyapi.registry.getDescriptor(descriptorTenant, 'authors')
|
|
181
|
+
const relationshipKey = booksDescriptor?.manyToMany?.authors?.relationship
|
|
182
|
+
const inverseRelationshipKey = authorsDescriptor?.manyToMany?.books?.relationship
|
|
183
|
+
storageMode.registerLink(
|
|
184
184
|
`${tablePrefix}_book_authors`,
|
|
185
185
|
'books',
|
|
186
186
|
'authors',
|
|
187
187
|
relationshipKey,
|
|
188
|
-
inverseRelationshipKey
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
inverseRelationshipKey
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
191
|
|
|
192
|
-
return api
|
|
192
|
+
return api
|
|
193
193
|
} finally {
|
|
194
194
|
if (storageMode.isAnyApi()) {
|
|
195
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
195
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
201
|
-
const apiName = pluginOptions.apiName || 'access-test-api'
|
|
202
|
-
const tablePrefix = pluginOptions.tablePrefix || 'access'
|
|
200
|
+
export async function createAccessControlApi (knex, pluginOptions = {}) {
|
|
201
|
+
const apiName = pluginOptions.apiName || 'access-test-api'
|
|
202
|
+
const tablePrefix = pluginOptions.tablePrefix || 'access'
|
|
203
203
|
if (storageMode.isAnyApi()) {
|
|
204
|
-
storageMode.clearRegistry()
|
|
204
|
+
storageMode.clearRegistry()
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
const api = new Api({
|
|
208
208
|
name: apiName,
|
|
209
209
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
210
|
-
})
|
|
210
|
+
})
|
|
211
211
|
|
|
212
|
-
const previousTenant = storageMode.currentTenant
|
|
212
|
+
const previousTenant = storageMode.currentTenant
|
|
213
213
|
const tenantId = storageMode.isAnyApi()
|
|
214
214
|
? (pluginOptions.tenantId || `${tablePrefix}_tenant`)
|
|
215
|
-
: storageMode.defaultTenant
|
|
215
|
+
: storageMode.defaultTenant
|
|
216
216
|
if (storageMode.isAnyApi()) {
|
|
217
|
-
storageMode.setCurrentTenant(tenantId)
|
|
217
|
+
storageMode.setCurrentTenant(tenantId)
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
await api.use(RestApiPlugin, {
|
|
@@ -231,12 +231,12 @@ export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
|
231
231
|
patch: 'minimal'
|
|
232
232
|
},
|
|
233
233
|
sortableFields: ['id', 'name', 'user_id']
|
|
234
|
-
})
|
|
235
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
236
|
-
await resetAnyApiTables(knex)
|
|
234
|
+
})
|
|
235
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
236
|
+
await resetAnyApiTables(knex)
|
|
237
237
|
|
|
238
238
|
try {
|
|
239
|
-
await api.use(AccessPlugin, pluginOptions.access || {})
|
|
239
|
+
await api.use(AccessPlugin, pluginOptions.access || {})
|
|
240
240
|
|
|
241
241
|
await api.addResource('users', {
|
|
242
242
|
schema: {
|
|
@@ -245,9 +245,9 @@ export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
|
245
245
|
display_name: { type: 'string', nullable: true }
|
|
246
246
|
},
|
|
247
247
|
tableName: `${tablePrefix}_users`
|
|
248
|
-
})
|
|
249
|
-
await api.resources.users.createKnexTable()
|
|
250
|
-
mapTable(`${tablePrefix}_users`, 'users')
|
|
248
|
+
})
|
|
249
|
+
await api.resources.users.createKnexTable()
|
|
250
|
+
mapTable(`${tablePrefix}_users`, 'users')
|
|
251
251
|
|
|
252
252
|
await api.addResource('projects', {
|
|
253
253
|
schema: {
|
|
@@ -268,9 +268,9 @@ export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
|
268
268
|
},
|
|
269
269
|
ownership: true,
|
|
270
270
|
tableName: `${tablePrefix}_projects`
|
|
271
|
-
})
|
|
272
|
-
await api.resources.projects.createKnexTable()
|
|
273
|
-
mapTable(`${tablePrefix}_projects`, 'projects')
|
|
271
|
+
})
|
|
272
|
+
await api.resources.projects.createKnexTable()
|
|
273
|
+
mapTable(`${tablePrefix}_projects`, 'projects')
|
|
274
274
|
|
|
275
275
|
await api.addResource('notes', {
|
|
276
276
|
schema: {
|
|
@@ -292,14 +292,14 @@ export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
|
292
292
|
},
|
|
293
293
|
ownership: true,
|
|
294
294
|
tableName: `${tablePrefix}_notes`
|
|
295
|
-
})
|
|
296
|
-
await api.resources.notes.createKnexTable()
|
|
297
|
-
mapTable(`${tablePrefix}_notes`, 'notes')
|
|
295
|
+
})
|
|
296
|
+
await api.resources.notes.createKnexTable()
|
|
297
|
+
mapTable(`${tablePrefix}_notes`, 'notes')
|
|
298
298
|
|
|
299
|
-
return api
|
|
299
|
+
return api
|
|
300
300
|
} finally {
|
|
301
301
|
if (storageMode.isAnyApi()) {
|
|
302
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
302
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
}
|
|
@@ -307,13 +307,13 @@ export async function createAccessControlApi(knex, pluginOptions = {}) {
|
|
|
307
307
|
/**
|
|
308
308
|
* Creates a basic API with bulk operations enabled
|
|
309
309
|
*/
|
|
310
|
-
export async function createBulkOperationsApi(knex, pluginOptions = {}) {
|
|
311
|
-
const { BulkOperationsPlugin } = await import('../../plugins/core/bulk-operations-plugin.js')
|
|
312
|
-
|
|
310
|
+
export async function createBulkOperationsApi (knex, pluginOptions = {}) {
|
|
311
|
+
const { BulkOperationsPlugin } = await import('../../plugins/core/bulk-operations-plugin.js')
|
|
312
|
+
|
|
313
313
|
const api = await createBasicApi(knex, {
|
|
314
314
|
...pluginOptions,
|
|
315
315
|
tenantId: pluginOptions.tenantId || 'bulk_ops_tenant'
|
|
316
|
-
})
|
|
316
|
+
})
|
|
317
317
|
|
|
318
318
|
// Add bulk operations plugin
|
|
319
319
|
await api.use(BulkOperationsPlugin, {
|
|
@@ -324,20 +324,20 @@ export async function createBulkOperationsApi(knex, pluginOptions = {}) {
|
|
|
324
324
|
enableOptimizations: true,
|
|
325
325
|
...pluginOptions['bulk-operations']
|
|
326
326
|
}
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
return api
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return api
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
333
|
* Creates an extended API with additional fields for more complex testing
|
|
334
334
|
*/
|
|
335
|
-
export async function createExtendedApi(knex) {
|
|
335
|
+
export async function createExtendedApi (knex) {
|
|
336
336
|
const api = new Api({
|
|
337
337
|
name: 'extended-test-api',
|
|
338
|
-
})
|
|
338
|
+
})
|
|
339
339
|
|
|
340
|
-
const tenantId = storageMode.isAnyApi() ? 'extended_tenant' : storageMode.defaultTenant
|
|
340
|
+
const tenantId = storageMode.isAnyApi() ? 'extended_tenant' : storageMode.defaultTenant
|
|
341
341
|
|
|
342
342
|
await api.use(RestApiPlugin, {
|
|
343
343
|
simplifiedApi: false,
|
|
@@ -353,164 +353,164 @@ export async function createExtendedApi(knex) {
|
|
|
353
353
|
patch: 'minimal' // Tests expect minimal response with ID
|
|
354
354
|
},
|
|
355
355
|
sortableFields: ['id', 'title', 'country_id', 'publisher_id', 'price', 'language', 'population', 'name', 'code']
|
|
356
|
-
})
|
|
356
|
+
})
|
|
357
357
|
await withTenantContext(tenantId, async () => {
|
|
358
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
359
|
-
await resetAnyApiTables(knex)
|
|
358
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
359
|
+
await resetAnyApiTables(knex)
|
|
360
360
|
|
|
361
361
|
// Countries with extended fields
|
|
362
362
|
await api.addResource('countries', {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
363
|
+
schema: {
|
|
364
|
+
id: { type: 'id' },
|
|
365
|
+
name: { type: 'string', required: true, max: 100 },
|
|
366
|
+
code: { type: 'string', max: 2, unique: true },
|
|
367
|
+
capital: { type: 'string', max: 100 },
|
|
368
|
+
population: { type: 'number' },
|
|
369
|
+
currency: { type: 'string', max: 3 }
|
|
370
|
+
},
|
|
371
|
+
relationships: {
|
|
372
|
+
publishers: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
|
|
373
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'country_id' },
|
|
374
|
+
authors: { type: 'hasMany', target: 'authors', foreignKey: 'nationality_id' }
|
|
375
|
+
},
|
|
376
|
+
tableName: 'ext_countries'
|
|
377
|
+
})
|
|
378
|
+
await api.resources.countries.createKnexTable()
|
|
379
|
+
mapTable('ext_countries', 'countries')
|
|
380
380
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
381
|
+
// Publishers with extended fields
|
|
382
|
+
await api.addResource('publishers', {
|
|
383
|
+
schema: {
|
|
384
|
+
id: { type: 'id' },
|
|
385
|
+
name: { type: 'string', required: true, max: 200 },
|
|
386
|
+
country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' },
|
|
387
|
+
founded_year: { type: 'number' },
|
|
388
|
+
website: { type: 'string', max: 255 },
|
|
389
|
+
active: { type: 'boolean', default: true }
|
|
390
|
+
},
|
|
391
|
+
relationships: {
|
|
392
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' },
|
|
393
|
+
reviews: {
|
|
394
|
+
type: 'hasMany',
|
|
395
|
+
target: 'reviews',
|
|
396
|
+
via: 'reviewable'
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
tableName: 'ext_publishers'
|
|
400
|
+
})
|
|
401
|
+
await api.resources.publishers.createKnexTable()
|
|
402
|
+
mapTable('ext_publishers', 'publishers')
|
|
403
403
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
404
|
+
// Authors with extended fields
|
|
405
|
+
await api.addResource('authors', {
|
|
406
|
+
schema: {
|
|
407
|
+
id: { type: 'id' },
|
|
408
|
+
name: { type: 'string', required: true, max: 200 },
|
|
409
|
+
birth_date: { type: 'date' },
|
|
410
|
+
biography: { type: 'string', max: 5000 },
|
|
411
|
+
nationality_id: { type: 'number', belongsTo: 'countries', as: 'nationality' }
|
|
412
|
+
},
|
|
413
|
+
relationships: {
|
|
414
|
+
books: { type: 'manyToMany', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' },
|
|
415
|
+
reviews: {
|
|
416
|
+
type: 'hasMany',
|
|
417
|
+
target: 'reviews',
|
|
418
|
+
via: 'reviewable'
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
tableName: 'ext_authors'
|
|
422
|
+
})
|
|
423
|
+
await api.resources.authors.createKnexTable()
|
|
424
|
+
mapTable('ext_authors', 'authors')
|
|
425
425
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
426
|
+
// Books with extended fields
|
|
427
|
+
await api.addResource('books', {
|
|
428
|
+
schema: {
|
|
429
|
+
id: { type: 'id' },
|
|
430
|
+
title: { type: 'string', required: true, max: 300, search: true },
|
|
431
|
+
isbn: { type: 'string', max: 13 },
|
|
432
|
+
pages: { type: 'number' },
|
|
433
|
+
price: { type: 'number', search: true }, // Store price as string for decimal precision
|
|
434
|
+
published_date: { type: 'date' },
|
|
435
|
+
language: { type: 'string', max: 2, default: 'en', search: true },
|
|
436
|
+
country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country', search: true },
|
|
437
|
+
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher', search: true }
|
|
438
|
+
},
|
|
439
|
+
relationships: {
|
|
440
|
+
authors: { type: 'manyToMany', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' },
|
|
441
|
+
reviews: {
|
|
442
|
+
type: 'hasMany',
|
|
443
|
+
target: 'reviews',
|
|
444
|
+
via: 'reviewable'
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
tableName: 'ext_books'
|
|
448
|
+
})
|
|
449
|
+
await api.resources.books.createKnexTable()
|
|
450
|
+
mapTable('ext_books', 'books')
|
|
451
451
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
452
|
+
// Book-Authors pivot with extended fields
|
|
453
|
+
await api.addResource('book_authors', {
|
|
454
|
+
schema: {
|
|
455
|
+
id: { type: 'id' },
|
|
456
|
+
book_id: { type: 'number', required: true, belongsTo: 'books', as: 'book' },
|
|
457
|
+
author_id: { type: 'number', required: true, belongsTo: 'authors', as: 'author' },
|
|
458
|
+
contribution_type: { type: 'string', max: 50 },
|
|
459
|
+
order: { type: 'number' }
|
|
460
|
+
},
|
|
461
|
+
tableName: 'ext_book_authors'
|
|
462
|
+
})
|
|
463
|
+
await api.resources.book_authors.createKnexTable()
|
|
464
|
+
mapTable('ext_book_authors', 'book_authors')
|
|
465
|
+
if (storageMode.isAnyApi()) {
|
|
466
|
+
const booksDescriptor = await api.anyapi.registry.getDescriptor(tenantId, 'books')
|
|
467
|
+
const authorsDescriptor = await api.anyapi.registry.getDescriptor(tenantId, 'authors')
|
|
468
|
+
const relationshipKey = booksDescriptor?.manyToMany?.authors?.relationship
|
|
469
|
+
const inverseRelationshipKey = authorsDescriptor?.manyToMany?.books?.relationship
|
|
470
|
+
storageMode.registerLink('ext_book_authors', 'books', 'authors', relationshipKey, inverseRelationshipKey)
|
|
471
|
+
}
|
|
472
472
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
473
|
+
// Polymorphic reviews (can go on authors, books and publishers)
|
|
474
|
+
await api.addResource('reviews', {
|
|
475
|
+
schema: {
|
|
476
|
+
id: { type: 'id' },
|
|
477
|
+
rating: { type: 'number', required: true, min: 1, max: 5 },
|
|
478
|
+
title: { type: 'string', max: 200 },
|
|
479
|
+
content: { type: 'string', required: true, max: 5000 },
|
|
480
|
+
reviewer_name: { type: 'string', required: true, max: 100 },
|
|
481
|
+
review_date: { type: 'dateTime', default: 'now()' },
|
|
482
|
+
helpful_count: { type: 'number', default: 0 },
|
|
483
|
+
reviewable_type: { type: 'string', required: true },
|
|
484
|
+
reviewable_id: { type: 'number', required: true }
|
|
485
|
+
},
|
|
486
|
+
relationships: {
|
|
487
|
+
reviewable: {
|
|
488
|
+
belongsToPolymorphic: {
|
|
489
|
+
types: ['books', 'authors', 'publishers'],
|
|
490
|
+
typeField: 'reviewable_type',
|
|
491
|
+
idField: 'reviewable_id'
|
|
492
|
+
}
|
|
492
493
|
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
});
|
|
494
|
+
},
|
|
495
|
+
tableName: 'ext_reviews'
|
|
496
|
+
})
|
|
497
|
+
await api.resources.reviews.createKnexTable()
|
|
498
|
+
mapTable('ext_reviews', 'reviews')
|
|
499
|
+
})
|
|
500
500
|
|
|
501
|
-
return api
|
|
501
|
+
return api
|
|
502
502
|
}
|
|
503
503
|
|
|
504
504
|
/**
|
|
505
505
|
* Creates an API with limited include depth for testing depth validation
|
|
506
506
|
* Uses 'limited_' prefix for all tables to avoid conflicts
|
|
507
507
|
*/
|
|
508
|
-
export async function createLimitedDepthApi(knex) {
|
|
508
|
+
export async function createLimitedDepthApi (knex) {
|
|
509
509
|
const api = new Api({
|
|
510
510
|
name: 'limited-depth-api',
|
|
511
|
-
})
|
|
511
|
+
})
|
|
512
512
|
|
|
513
|
-
const tenantId = storageMode.isAnyApi() ? 'limited_depth_tenant' : storageMode.defaultTenant
|
|
513
|
+
const tenantId = storageMode.isAnyApi() ? 'limited_depth_tenant' : storageMode.defaultTenant
|
|
514
514
|
|
|
515
515
|
await api.use(RestApiPlugin, {
|
|
516
516
|
simplifiedApi: false,
|
|
@@ -527,99 +527,99 @@ export async function createLimitedDepthApi(knex) {
|
|
|
527
527
|
},
|
|
528
528
|
sortableFields: ['id', 'title', 'country_id', 'publisher_id', 'name', 'code'],
|
|
529
529
|
includeDepthLimit: 2 // Key difference: limit is 2 instead of default 3
|
|
530
|
-
})
|
|
530
|
+
})
|
|
531
531
|
await withTenantContext(tenantId, async () => {
|
|
532
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
532
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
533
533
|
|
|
534
534
|
// Use different table names with 'limited_' prefix to avoid conflicts
|
|
535
535
|
await api.addResource('countries', {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
536
|
+
schema: {
|
|
537
|
+
id: { type: 'id' },
|
|
538
|
+
name: { type: 'string', required: true, max: 100 },
|
|
539
|
+
code: { type: 'string', max: 2, unique: true }
|
|
540
|
+
},
|
|
541
|
+
relationships: {
|
|
542
|
+
publishers: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
|
|
543
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'country_id' }
|
|
544
|
+
},
|
|
545
|
+
tableName: 'limited_countries',
|
|
546
|
+
})
|
|
547
|
+
await api.resources.countries.createKnexTable()
|
|
548
|
+
mapTable('limited_countries', 'countries')
|
|
549
549
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
550
|
+
await api.addResource('publishers', {
|
|
551
|
+
schema: {
|
|
552
|
+
id: { type: 'id' },
|
|
553
|
+
name: { type: 'string', required: true, max: 200 },
|
|
554
|
+
country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' }
|
|
555
|
+
},
|
|
556
|
+
relationships: {
|
|
557
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' },
|
|
558
|
+
authors: { type: 'hasMany', target: 'authors', foreignKey: 'publisher_id' }
|
|
559
|
+
},
|
|
560
|
+
tableName: 'limited_publishers'
|
|
561
|
+
})
|
|
562
|
+
await api.resources.publishers.createKnexTable()
|
|
563
|
+
mapTable('limited_publishers', 'publishers')
|
|
564
564
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
565
|
+
await api.addResource('authors', {
|
|
566
|
+
schema: {
|
|
567
|
+
id: { type: 'id' },
|
|
568
|
+
name: { type: 'string', required: true, max: 200 },
|
|
569
|
+
publisher_id: { type: 'number', nullable: true, belongsTo: 'publishers', as: 'publisher' }
|
|
570
|
+
},
|
|
571
|
+
relationships: {
|
|
572
|
+
books: { type: 'manyToMany', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' }
|
|
573
|
+
},
|
|
574
|
+
tableName: 'limited_authors'
|
|
575
|
+
})
|
|
576
|
+
await api.resources.authors.createKnexTable()
|
|
577
577
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
578
|
+
await api.addResource('books', {
|
|
579
|
+
schema: {
|
|
580
|
+
id: { type: 'id' },
|
|
581
|
+
title: { type: 'string', required: true, max: 300, search: true },
|
|
582
|
+
country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country', search: true },
|
|
583
|
+
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher', search: true }
|
|
584
|
+
},
|
|
585
|
+
relationships: {
|
|
586
|
+
authors: { type: 'manyToMany', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' }
|
|
587
|
+
},
|
|
588
|
+
tableName: 'limited_books'
|
|
589
|
+
})
|
|
590
|
+
await api.resources.books.createKnexTable()
|
|
591
591
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
})
|
|
592
|
+
await api.addResource('book_authors', {
|
|
593
|
+
schema: {
|
|
594
|
+
id: { type: 'id' },
|
|
595
|
+
book_id: { type: 'number', required: true, belongsTo: 'books', as: 'book' },
|
|
596
|
+
author_id: { type: 'number', required: true, belongsTo: 'authors', as: 'author' }
|
|
597
|
+
},
|
|
598
|
+
tableName: 'limited_book_authors'
|
|
599
|
+
})
|
|
600
|
+
await api.resources.book_authors.createKnexTable()
|
|
601
|
+
mapTable('limited_book_authors', 'book_authors')
|
|
602
|
+
if (storageMode.isAnyApi()) {
|
|
603
|
+
const booksDescriptor = await api.anyapi.registry.getDescriptor(tenantId, 'books')
|
|
604
|
+
const authorsDescriptor = await api.anyapi.registry.getDescriptor(tenantId, 'authors')
|
|
605
|
+
const relationshipKey = booksDescriptor?.manyToMany?.authors?.relationship
|
|
606
|
+
const inverseRelationshipKey = authorsDescriptor?.manyToMany?.books?.relationship
|
|
607
|
+
storageMode.registerLink('limited_book_authors', 'books', 'authors', relationshipKey, inverseRelationshipKey)
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
610
|
|
|
611
|
-
return api
|
|
611
|
+
return api
|
|
612
612
|
}
|
|
613
613
|
|
|
614
614
|
/**
|
|
615
615
|
* Creates an API configuration for pagination testing
|
|
616
616
|
*/
|
|
617
|
-
export async function createPaginationApi(knex, options = {}) {
|
|
617
|
+
export async function createPaginationApi (knex, options = {}) {
|
|
618
618
|
const api = new Api({
|
|
619
619
|
name: 'pagination-test-api',
|
|
620
|
-
})
|
|
620
|
+
})
|
|
621
621
|
|
|
622
|
-
const tenantId = storageMode.isAnyApi() ? 'pagination_tenant' : storageMode.defaultTenant
|
|
622
|
+
const tenantId = storageMode.isAnyApi() ? 'pagination_tenant' : storageMode.defaultTenant
|
|
623
623
|
|
|
624
624
|
const restApiOptions = {
|
|
625
625
|
simplifiedApi: false,
|
|
@@ -636,66 +636,66 @@ export async function createPaginationApi(knex, options = {}) {
|
|
|
636
636
|
},
|
|
637
637
|
sortableFields: ['id', 'title', 'country_id', 'publisher_id', 'name', 'code'],
|
|
638
638
|
...options // Allow overriding options likereturnBasePath, enablePaginationCounts
|
|
639
|
-
}
|
|
639
|
+
}
|
|
640
640
|
|
|
641
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
641
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
642
642
|
await withTenantContext(tenantId, async () => {
|
|
643
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
643
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
644
644
|
|
|
645
645
|
// Countries table
|
|
646
646
|
await api.addResource('countries', {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
647
|
+
schema: {
|
|
648
|
+
id: { type: 'id' },
|
|
649
|
+
name: { type: 'string', required: true, max: 100 },
|
|
650
|
+
code: { type: 'string', max: 2, unique: true }
|
|
651
|
+
},
|
|
652
|
+
relationships: {
|
|
653
|
+
publishers: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
|
|
654
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'country_id' }
|
|
655
|
+
},
|
|
656
|
+
tableName: 'pagination_countries'
|
|
657
|
+
})
|
|
658
|
+
await api.resources.countries.createKnexTable()
|
|
659
|
+
mapTable('pagination_countries', 'countries')
|
|
660
660
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
661
|
+
// Publishers table
|
|
662
|
+
await api.addResource('publishers', {
|
|
663
|
+
schema: {
|
|
664
|
+
id: { type: 'id' },
|
|
665
|
+
name: { type: 'string', required: true, max: 200 },
|
|
666
|
+
country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' }
|
|
667
|
+
},
|
|
668
|
+
relationships: {
|
|
669
|
+
books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' }
|
|
670
|
+
},
|
|
671
|
+
tableName: 'pagination_publishers'
|
|
672
|
+
})
|
|
673
|
+
await api.resources.publishers.createKnexTable()
|
|
674
|
+
mapTable('pagination_publishers', 'publishers')
|
|
675
675
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
})
|
|
676
|
+
// Books table
|
|
677
|
+
await api.addResource('books', {
|
|
678
|
+
schema: {
|
|
679
|
+
id: { type: 'id' },
|
|
680
|
+
title: { type: 'string', required: true, max: 300, search: true },
|
|
681
|
+
country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country', search: true },
|
|
682
|
+
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher', search: true }
|
|
683
|
+
},
|
|
684
|
+
tableName: 'pagination_books'
|
|
685
|
+
})
|
|
686
|
+
await api.resources.books.createKnexTable()
|
|
687
|
+
mapTable('pagination_books', 'books')
|
|
688
|
+
})
|
|
689
689
|
|
|
690
|
-
return api
|
|
690
|
+
return api
|
|
691
691
|
}
|
|
692
692
|
|
|
693
693
|
/**
|
|
694
694
|
* Creates an API with WebSocket/Socket.IO support for testing
|
|
695
695
|
*/
|
|
696
|
-
export async function createWebSocketApi(knex, pluginOptions = {}) {
|
|
697
|
-
const { SocketIOPlugin } = await import('../../plugins/core/socketio-plugin.js')
|
|
698
|
-
const { jwtVerify } = await import('jose')
|
|
696
|
+
export async function createWebSocketApi (knex, pluginOptions = {}) {
|
|
697
|
+
const { SocketIOPlugin } = await import('../../plugins/core/socketio-plugin.js')
|
|
698
|
+
const { jwtVerify } = await import('jose')
|
|
699
699
|
|
|
700
700
|
const api = await createBasicApi(knex, {
|
|
701
701
|
...pluginOptions,
|
|
@@ -704,91 +704,91 @@ export async function createWebSocketApi(knex, pluginOptions = {}) {
|
|
|
704
704
|
express: {
|
|
705
705
|
port: 0 // Let OS assign a port
|
|
706
706
|
}
|
|
707
|
-
})
|
|
707
|
+
})
|
|
708
708
|
|
|
709
|
-
const socketioOptions = { ...(pluginOptions['socketio'] || {}) }
|
|
710
|
-
const authOptions = { ...(socketioOptions.auth || {}) }
|
|
711
|
-
const sharedSecret = authOptions.sharedSecret || socketioOptions.sharedSecret || 'test-secret-key'
|
|
709
|
+
const socketioOptions = { ...(pluginOptions['socketio'] || {}) }
|
|
710
|
+
const authOptions = { ...(socketioOptions.auth || {}) }
|
|
711
|
+
const sharedSecret = authOptions.sharedSecret || socketioOptions.sharedSecret || 'test-secret-key'
|
|
712
712
|
|
|
713
713
|
if (!authOptions.authenticate) {
|
|
714
714
|
authOptions.authenticate = async ({ socket }) => {
|
|
715
|
-
const token = socket.handshake.auth?.token
|
|
715
|
+
const token = socket.handshake.auth?.token
|
|
716
716
|
if (!token) {
|
|
717
|
-
throw new Error('Authentication required')
|
|
717
|
+
throw new Error('Authentication required')
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
-
const encoder = new TextEncoder()
|
|
721
|
-
const key = encoder.encode(sharedSecret)
|
|
722
|
-
const { payload } = await jwtVerify(token, key)
|
|
720
|
+
const encoder = new TextEncoder()
|
|
721
|
+
const key = encoder.encode(sharedSecret)
|
|
722
|
+
const { payload } = await jwtVerify(token, key)
|
|
723
723
|
|
|
724
724
|
const roles = Array.isArray(payload.roles)
|
|
725
725
|
? payload.roles
|
|
726
|
-
: payload.role ? [payload.role] : []
|
|
726
|
+
: payload.role ? [payload.role] : []
|
|
727
727
|
|
|
728
728
|
const authContext = {
|
|
729
729
|
userId: payload.userId || payload.sub,
|
|
730
730
|
roles,
|
|
731
731
|
token: payload
|
|
732
|
-
}
|
|
732
|
+
}
|
|
733
733
|
|
|
734
734
|
if (!authContext.userId) {
|
|
735
|
-
throw new Error('Token is missing required user identifier')
|
|
735
|
+
throw new Error('Token is missing required user identifier')
|
|
736
736
|
}
|
|
737
737
|
|
|
738
|
-
return authContext
|
|
739
|
-
}
|
|
738
|
+
return authContext
|
|
739
|
+
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
742
|
if (authOptions.requireAuth === undefined) {
|
|
743
|
-
authOptions.requireAuth = true
|
|
743
|
+
authOptions.requireAuth = true
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
-
socketioOptions.auth = authOptions
|
|
746
|
+
socketioOptions.auth = authOptions
|
|
747
|
+
|
|
748
|
+
await api.use(SocketIOPlugin, socketioOptions)
|
|
747
749
|
|
|
748
|
-
await api.use(SocketIOPlugin, socketioOptions);
|
|
749
|
-
|
|
750
750
|
// Create and start Express server
|
|
751
|
-
const app = express()
|
|
752
|
-
api.http.express.app = app
|
|
753
|
-
|
|
751
|
+
const app = express()
|
|
752
|
+
api.http.express.app = app
|
|
753
|
+
|
|
754
754
|
// Mount the API routes
|
|
755
|
-
app.use('/api', api.http.express.router)
|
|
756
|
-
|
|
755
|
+
app.use('/api', api.http.express.router)
|
|
756
|
+
|
|
757
757
|
// Create HTTP server
|
|
758
|
-
const server = createServer(app)
|
|
759
|
-
|
|
758
|
+
const server = createServer(app)
|
|
759
|
+
|
|
760
760
|
// Start Socket.IO server
|
|
761
|
-
await api.startSocketServer(server)
|
|
762
|
-
|
|
761
|
+
await api.startSocketServer(server)
|
|
762
|
+
|
|
763
763
|
// Start listening
|
|
764
764
|
await new Promise((resolve) => {
|
|
765
765
|
server.listen(0, () => {
|
|
766
|
-
resolve()
|
|
767
|
-
})
|
|
768
|
-
})
|
|
769
|
-
|
|
770
|
-
return { api, server }
|
|
766
|
+
resolve()
|
|
767
|
+
})
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
return { api, server }
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
/**
|
|
774
774
|
* Creates an API with computed fields for testing
|
|
775
775
|
*/
|
|
776
|
-
export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
776
|
+
export async function createComputedFieldsApi (knex, pluginOptions = {}) {
|
|
777
777
|
const api = new Api({
|
|
778
778
|
name: 'computed-fields-test-api',
|
|
779
779
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
780
|
-
})
|
|
780
|
+
})
|
|
781
781
|
|
|
782
|
-
const tenantId = storageMode.isAnyApi() ? 'computed_fields_tenant' : storageMode.defaultTenant
|
|
782
|
+
const tenantId = storageMode.isAnyApi() ? 'computed_fields_tenant' : storageMode.defaultTenant
|
|
783
783
|
|
|
784
784
|
await api.use(RestApiPlugin, {
|
|
785
785
|
simplifiedApi: true,
|
|
786
786
|
simplifiedTransport: true
|
|
787
|
-
})
|
|
787
|
+
})
|
|
788
788
|
|
|
789
789
|
await withTenantContext(tenantId, async () => {
|
|
790
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
791
|
-
await resetAnyApiTables(knex)
|
|
790
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
791
|
+
await resetAnyApiTables(knex)
|
|
792
792
|
|
|
793
793
|
// Products resource with computed fields
|
|
794
794
|
await api.addResource('products', {
|
|
@@ -803,8 +803,8 @@ export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
|
803
803
|
computed: true,
|
|
804
804
|
dependencies: ['price', 'cost'],
|
|
805
805
|
compute: ({ attributes }) => {
|
|
806
|
-
if (!attributes.price || attributes.price === 0) return 0
|
|
807
|
-
return Number(((attributes.price - attributes.cost) / attributes.price * 100).toFixed(2))
|
|
806
|
+
if (!attributes.price || attributes.price === 0) return 0
|
|
807
|
+
return Number(((attributes.price - attributes.cost) / attributes.price * 100).toFixed(2))
|
|
808
808
|
}
|
|
809
809
|
},
|
|
810
810
|
profit_amount: {
|
|
@@ -812,7 +812,7 @@ export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
|
812
812
|
computed: true,
|
|
813
813
|
dependencies: ['price', 'cost'],
|
|
814
814
|
compute: ({ attributes }) => {
|
|
815
|
-
return Number((attributes.price - attributes.cost).toFixed(2))
|
|
815
|
+
return Number((attributes.price - attributes.cost).toFixed(2))
|
|
816
816
|
}
|
|
817
817
|
}
|
|
818
818
|
},
|
|
@@ -820,10 +820,10 @@ export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
|
820
820
|
reviews: { type: 'hasMany', target: 'reviews', foreignKey: 'product_id' }
|
|
821
821
|
},
|
|
822
822
|
tableName: 'test_products'
|
|
823
|
-
})
|
|
824
|
-
await api.resources.products.createKnexTable()
|
|
825
|
-
mapTable('searchmerge_products', 'products')
|
|
826
|
-
mapTable('test_products', 'products')
|
|
823
|
+
})
|
|
824
|
+
await api.resources.products.createKnexTable()
|
|
825
|
+
mapTable('searchmerge_products', 'products')
|
|
826
|
+
mapTable('test_products', 'products')
|
|
827
827
|
|
|
828
828
|
// Reviews resource with computed fields
|
|
829
829
|
await api.addResource('reviews', {
|
|
@@ -841,8 +841,8 @@ export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
|
841
841
|
computed: true,
|
|
842
842
|
dependencies: ['helpful_votes', 'total_votes'],
|
|
843
843
|
compute: ({ attributes }) => {
|
|
844
|
-
if (attributes.total_votes === 0) return null
|
|
845
|
-
return Number(((attributes.helpful_votes / attributes.total_votes) * 100).toFixed(0))
|
|
844
|
+
if (attributes.total_votes === 0) return null
|
|
845
|
+
return Number(((attributes.helpful_votes / attributes.total_votes) * 100).toFixed(0))
|
|
846
846
|
}
|
|
847
847
|
},
|
|
848
848
|
is_helpful: {
|
|
@@ -850,49 +850,49 @@ export async function createComputedFieldsApi(knex, pluginOptions = {}) {
|
|
|
850
850
|
computed: true,
|
|
851
851
|
dependencies: ['helpful_votes', 'total_votes', 'spam_score'],
|
|
852
852
|
compute: ({ attributes }) => {
|
|
853
|
-
if (attributes.total_votes < 10) return null
|
|
854
|
-
const helpfulnessScore = (attributes.helpful_votes / attributes.total_votes) * 100
|
|
855
|
-
return helpfulnessScore > 70 && attributes.spam_score < 0.5
|
|
853
|
+
if (attributes.total_votes < 10) return null
|
|
854
|
+
const helpfulnessScore = (attributes.helpful_votes / attributes.total_votes) * 100
|
|
855
|
+
return helpfulnessScore > 70 && attributes.spam_score < 0.5
|
|
856
856
|
}
|
|
857
857
|
}
|
|
858
858
|
},
|
|
859
859
|
tableName: 'test_reviews'
|
|
860
|
-
})
|
|
861
|
-
await api.resources.reviews.createKnexTable()
|
|
862
|
-
mapTable('test_reviews', 'reviews')
|
|
863
|
-
})
|
|
860
|
+
})
|
|
861
|
+
await api.resources.reviews.createKnexTable()
|
|
862
|
+
mapTable('test_reviews', 'reviews')
|
|
863
|
+
})
|
|
864
864
|
|
|
865
|
-
return api
|
|
865
|
+
return api
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
/**
|
|
869
869
|
* Creates an API with field getters for testing
|
|
870
870
|
*/
|
|
871
|
-
export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
871
|
+
export async function createFieldGettersApi (knex, pluginOptions = {}) {
|
|
872
872
|
const api = new Api({
|
|
873
873
|
name: 'field-getters-test-api',
|
|
874
874
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
875
|
-
})
|
|
875
|
+
})
|
|
876
876
|
|
|
877
|
-
const tenantId = storageMode.isAnyApi() ? 'field_getters_tenant' : storageMode.defaultTenant
|
|
878
|
-
const previousTenant = storageMode.currentTenant
|
|
877
|
+
const tenantId = storageMode.isAnyApi() ? 'field_getters_tenant' : storageMode.defaultTenant
|
|
878
|
+
const previousTenant = storageMode.currentTenant
|
|
879
879
|
if (storageMode.isAnyApi()) {
|
|
880
|
-
storageMode.setCurrentTenant(tenantId)
|
|
880
|
+
storageMode.setCurrentTenant(tenantId)
|
|
881
881
|
}
|
|
882
882
|
|
|
883
883
|
await api.use(RestApiPlugin, {
|
|
884
884
|
simplifiedApi: true,
|
|
885
885
|
simplifiedTransport: true
|
|
886
|
-
})
|
|
887
|
-
|
|
888
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
889
|
-
await resetAnyApiTables(knex)
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
889
|
+
await resetAnyApiTables(knex)
|
|
890
890
|
|
|
891
891
|
// Users resource with basic getters
|
|
892
892
|
await api.addResource('users', {
|
|
893
893
|
schema: {
|
|
894
894
|
id: { type: 'id' },
|
|
895
|
-
email: {
|
|
895
|
+
email: {
|
|
896
896
|
type: 'string',
|
|
897
897
|
nullable: true,
|
|
898
898
|
getter: (value) => value?.toLowerCase().trim()
|
|
@@ -906,24 +906,24 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
906
906
|
type: 'string',
|
|
907
907
|
nullable: true,
|
|
908
908
|
getter: (value) => {
|
|
909
|
-
if (!value) return null
|
|
909
|
+
if (!value) return null
|
|
910
910
|
// Remove all non-digits and format
|
|
911
|
-
const digits = value.replace(/\D/g, '')
|
|
911
|
+
const digits = value.replace(/\D/g, '')
|
|
912
912
|
if (digits.length === 10) {
|
|
913
|
-
return `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}
|
|
913
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`
|
|
914
914
|
}
|
|
915
|
-
return value
|
|
915
|
+
return value
|
|
916
916
|
}
|
|
917
917
|
},
|
|
918
918
|
metadata_json: {
|
|
919
919
|
type: 'string',
|
|
920
920
|
nullable: true,
|
|
921
921
|
getter: (value) => {
|
|
922
|
-
if (!value) return {}
|
|
922
|
+
if (!value) return {}
|
|
923
923
|
try {
|
|
924
|
-
return JSON.parse(value)
|
|
924
|
+
return JSON.parse(value)
|
|
925
925
|
} catch {
|
|
926
|
-
return {}
|
|
926
|
+
return {}
|
|
927
927
|
}
|
|
928
928
|
}
|
|
929
929
|
},
|
|
@@ -931,16 +931,16 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
931
931
|
type: 'string',
|
|
932
932
|
nullable: true,
|
|
933
933
|
getter: (value) => {
|
|
934
|
-
if (!value) return []
|
|
935
|
-
return value.split(',').map(t => t.trim()).filter(t => t)
|
|
934
|
+
if (!value) return []
|
|
935
|
+
return value.split(',').map(t => t.trim()).filter(t => t)
|
|
936
936
|
}
|
|
937
937
|
}
|
|
938
938
|
},
|
|
939
939
|
tableName: 'getter_users'
|
|
940
|
-
})
|
|
941
|
-
await api.resources.users.createKnexTable()
|
|
942
|
-
mapTable('searchmerge_users', 'users')
|
|
943
|
-
mapTable('getter_users', 'users')
|
|
940
|
+
})
|
|
941
|
+
await api.resources.users.createKnexTable()
|
|
942
|
+
mapTable('searchmerge_users', 'users')
|
|
943
|
+
mapTable('getter_users', 'users')
|
|
944
944
|
|
|
945
945
|
// Products resource with getters and computed fields
|
|
946
946
|
await api.addResource('products', {
|
|
@@ -954,8 +954,8 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
954
954
|
type: 'string',
|
|
955
955
|
getter: (value) => {
|
|
956
956
|
// Truncate long descriptions
|
|
957
|
-
if (!value) return ''
|
|
958
|
-
return value.length > 50 ? value.substring(0, 47) + '...' : value
|
|
957
|
+
if (!value) return ''
|
|
958
|
+
return value.length > 50 ? value.substring(0, 47) + '...' : value
|
|
959
959
|
}
|
|
960
960
|
},
|
|
961
961
|
price_str: {
|
|
@@ -972,7 +972,7 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
972
972
|
dependencies: ['price_str', 'tax_rate_str'],
|
|
973
973
|
compute: ({ attributes }) => {
|
|
974
974
|
// Should see numbers from getters, not strings
|
|
975
|
-
return attributes.price_str * (1 + attributes.tax_rate_str)
|
|
975
|
+
return attributes.price_str * (1 + attributes.tax_rate_str)
|
|
976
976
|
}
|
|
977
977
|
}
|
|
978
978
|
},
|
|
@@ -980,9 +980,9 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
980
980
|
reviews: { type: 'hasMany', target: 'reviews', foreignKey: 'product_id' }
|
|
981
981
|
},
|
|
982
982
|
tableName: 'getter_products'
|
|
983
|
-
})
|
|
984
|
-
await api.resources.products.createKnexTable()
|
|
985
|
-
mapTable('getter_products', 'products')
|
|
983
|
+
})
|
|
984
|
+
await api.resources.products.createKnexTable()
|
|
985
|
+
mapTable('getter_products', 'products')
|
|
986
986
|
|
|
987
987
|
// Reviews resource with getters
|
|
988
988
|
await api.addResource('reviews', {
|
|
@@ -996,9 +996,9 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
996
996
|
rating: { type: 'number' }
|
|
997
997
|
},
|
|
998
998
|
tableName: 'getter_reviews'
|
|
999
|
-
})
|
|
1000
|
-
await api.resources.reviews.createKnexTable()
|
|
1001
|
-
mapTable('getter_reviews', 'reviews')
|
|
999
|
+
})
|
|
1000
|
+
await api.resources.reviews.createKnexTable()
|
|
1001
|
+
mapTable('getter_reviews', 'reviews')
|
|
1002
1002
|
|
|
1003
1003
|
// Resource with getter dependencies
|
|
1004
1004
|
await api.addResource('formatted_data', {
|
|
@@ -1011,28 +1011,28 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
1011
1011
|
},
|
|
1012
1012
|
step2: {
|
|
1013
1013
|
type: 'string',
|
|
1014
|
-
nullable: true,
|
|
1014
|
+
nullable: true,
|
|
1015
1015
|
getter: (value, { attributes }) => {
|
|
1016
|
-
if (!value) return value
|
|
1016
|
+
if (!value) return value // Return null/undefined as-is
|
|
1017
1017
|
// Depends on step1 being trimmed first
|
|
1018
|
-
return `${value} [step1: ${attributes.step1}]
|
|
1018
|
+
return `${value} [step1: ${attributes.step1}]`
|
|
1019
1019
|
},
|
|
1020
1020
|
runGetterAfter: ['step1']
|
|
1021
1021
|
},
|
|
1022
1022
|
step3: {
|
|
1023
1023
|
type: 'string',
|
|
1024
1024
|
getter: (value, { attributes }) => {
|
|
1025
|
-
if (!value) return value
|
|
1025
|
+
if (!value) return value // Return null/undefined as-is
|
|
1026
1026
|
// Depends on step2
|
|
1027
|
-
return `${value} [step2: ${attributes.step2}]
|
|
1027
|
+
return `${value} [step2: ${attributes.step2}]`
|
|
1028
1028
|
},
|
|
1029
1029
|
runGetterAfter: ['step2']
|
|
1030
1030
|
}
|
|
1031
1031
|
},
|
|
1032
1032
|
tableName: 'getter_formatted'
|
|
1033
|
-
})
|
|
1034
|
-
await api.resources.formatted_data.createKnexTable()
|
|
1035
|
-
mapTable('getter_formatted', 'formatted_data')
|
|
1033
|
+
})
|
|
1034
|
+
await api.resources.formatted_data.createKnexTable()
|
|
1035
|
+
mapTable('getter_formatted', 'formatted_data')
|
|
1036
1036
|
|
|
1037
1037
|
// Async getters example
|
|
1038
1038
|
await api.addResource('encrypted_data', {
|
|
@@ -1042,48 +1042,48 @@ export async function createFieldGettersApi(knex, pluginOptions = {}) {
|
|
|
1042
1042
|
type: 'string',
|
|
1043
1043
|
nullable: true,
|
|
1044
1044
|
getter: async (value) => {
|
|
1045
|
-
if (!value) return null
|
|
1045
|
+
if (!value) return null
|
|
1046
1046
|
// Simulate async decryption
|
|
1047
|
-
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1048
|
-
return Buffer.from(value, 'base64').toString()
|
|
1047
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1048
|
+
return Buffer.from(value, 'base64').toString()
|
|
1049
1049
|
}
|
|
1050
1050
|
},
|
|
1051
1051
|
data: {
|
|
1052
1052
|
type: 'string',
|
|
1053
1053
|
nullable: true,
|
|
1054
1054
|
getter: async (value, context) => {
|
|
1055
|
-
if (!value) return null
|
|
1056
|
-
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1057
|
-
const decrypted = Buffer.from(value, 'base64').toString()
|
|
1058
|
-
return `[${context.scopeName}] ${decrypted}
|
|
1055
|
+
if (!value) return null
|
|
1056
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1057
|
+
const decrypted = Buffer.from(value, 'base64').toString()
|
|
1058
|
+
return `[${context.scopeName}] ${decrypted}`
|
|
1059
1059
|
}
|
|
1060
1060
|
}
|
|
1061
1061
|
},
|
|
1062
1062
|
tableName: 'getter_encrypted'
|
|
1063
|
-
})
|
|
1064
|
-
await api.resources.encrypted_data.createKnexTable()
|
|
1065
|
-
mapTable('getter_encrypted', 'encrypted_data')
|
|
1063
|
+
})
|
|
1064
|
+
await api.resources.encrypted_data.createKnexTable()
|
|
1065
|
+
mapTable('getter_encrypted', 'encrypted_data')
|
|
1066
1066
|
|
|
1067
1067
|
if (storageMode.isAnyApi()) {
|
|
1068
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
1068
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
1069
1069
|
}
|
|
1070
1070
|
|
|
1071
|
-
return api
|
|
1071
|
+
return api
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
1074
|
/**
|
|
1075
1075
|
* Creates an API with field setters for testing
|
|
1076
1076
|
*/
|
|
1077
|
-
export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
1077
|
+
export async function createFieldSettersApi (knex, pluginOptions = {}) {
|
|
1078
1078
|
const api = new Api({
|
|
1079
1079
|
name: 'field-setters-test-api',
|
|
1080
1080
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
1081
|
-
})
|
|
1081
|
+
})
|
|
1082
1082
|
|
|
1083
|
-
const tenantId = storageMode.isAnyApi() ? 'field_setters_tenant' : storageMode.defaultTenant
|
|
1084
|
-
const previousTenant = storageMode.currentTenant
|
|
1083
|
+
const tenantId = storageMode.isAnyApi() ? 'field_setters_tenant' : storageMode.defaultTenant
|
|
1084
|
+
const previousTenant = storageMode.currentTenant
|
|
1085
1085
|
if (storageMode.isAnyApi()) {
|
|
1086
|
-
storageMode.setCurrentTenant(tenantId)
|
|
1086
|
+
storageMode.setCurrentTenant(tenantId)
|
|
1087
1087
|
}
|
|
1088
1088
|
|
|
1089
1089
|
await api.use(RestApiPlugin, {
|
|
@@ -1094,16 +1094,16 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1094
1094
|
put: true,
|
|
1095
1095
|
patch: true
|
|
1096
1096
|
}
|
|
1097
|
-
})
|
|
1098
|
-
|
|
1099
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
1100
|
-
await resetAnyApiTables(knex)
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1099
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
1100
|
+
await resetAnyApiTables(knex)
|
|
1101
1101
|
|
|
1102
1102
|
// Users resource with basic setters
|
|
1103
1103
|
await api.addResource('users', {
|
|
1104
1104
|
schema: {
|
|
1105
1105
|
id: { type: 'id' },
|
|
1106
|
-
email: {
|
|
1106
|
+
email: {
|
|
1107
1107
|
type: 'string',
|
|
1108
1108
|
required: true,
|
|
1109
1109
|
setter: (value) => value?.toLowerCase().trim()
|
|
@@ -1118,13 +1118,13 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1118
1118
|
setter: (value) => value // Pass through
|
|
1119
1119
|
},
|
|
1120
1120
|
preferences: {
|
|
1121
|
-
type: 'string', // Accepts string, stored as string
|
|
1121
|
+
type: 'string', // Accepts string, stored as string
|
|
1122
1122
|
setter: (value) => value // Pass through
|
|
1123
1123
|
}
|
|
1124
1124
|
},
|
|
1125
1125
|
tableName: 'setter_users'
|
|
1126
|
-
})
|
|
1127
|
-
await api.resources.users.createKnexTable()
|
|
1126
|
+
})
|
|
1127
|
+
await api.resources.users.createKnexTable()
|
|
1128
1128
|
|
|
1129
1129
|
// Products with type conversion setters
|
|
1130
1130
|
await api.addResource('products', {
|
|
@@ -1137,7 +1137,7 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1137
1137
|
min: 0,
|
|
1138
1138
|
setter: (value) => {
|
|
1139
1139
|
// Convert to cents and round
|
|
1140
|
-
return Math.round(value * 100)
|
|
1140
|
+
return Math.round(value * 100)
|
|
1141
1141
|
}
|
|
1142
1142
|
},
|
|
1143
1143
|
discount_percent: {
|
|
@@ -1152,8 +1152,8 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1152
1152
|
}
|
|
1153
1153
|
},
|
|
1154
1154
|
tableName: 'setter_products'
|
|
1155
|
-
})
|
|
1156
|
-
await api.resources.products.createKnexTable()
|
|
1155
|
+
})
|
|
1156
|
+
await api.resources.products.createKnexTable()
|
|
1157
1157
|
|
|
1158
1158
|
// Secure data with async setters
|
|
1159
1159
|
await api.addResource('secure_data', {
|
|
@@ -1165,44 +1165,44 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1165
1165
|
min: 8,
|
|
1166
1166
|
setter: async (value) => {
|
|
1167
1167
|
// Simulate async password hashing
|
|
1168
|
-
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1169
|
-
return `hashed:${value}
|
|
1168
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1169
|
+
return `hashed:${value}`
|
|
1170
1170
|
}
|
|
1171
1171
|
},
|
|
1172
1172
|
api_key: {
|
|
1173
1173
|
type: 'string',
|
|
1174
1174
|
setter: async (value) => {
|
|
1175
|
-
if (!value) return null
|
|
1175
|
+
if (!value) return null
|
|
1176
1176
|
// Simulate encryption
|
|
1177
|
-
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1178
|
-
return Buffer.from(value).toString('base64')
|
|
1177
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
1178
|
+
return Buffer.from(value).toString('base64')
|
|
1179
1179
|
}
|
|
1180
1180
|
},
|
|
1181
1181
|
data: {
|
|
1182
1182
|
type: 'string',
|
|
1183
1183
|
setter: async (value) => {
|
|
1184
|
-
if (!value) return null
|
|
1185
|
-
return Buffer.from(value).toString('base64')
|
|
1184
|
+
if (!value) return null
|
|
1185
|
+
return Buffer.from(value).toString('base64')
|
|
1186
1186
|
}
|
|
1187
1187
|
}
|
|
1188
1188
|
},
|
|
1189
1189
|
tableName: 'setter_secure'
|
|
1190
|
-
})
|
|
1191
|
-
await api.resources.secure_data.createKnexTable()
|
|
1190
|
+
})
|
|
1191
|
+
await api.resources.secure_data.createKnexTable()
|
|
1192
1192
|
|
|
1193
1193
|
// Resource with setter dependencies
|
|
1194
1194
|
await api.addResource('computed_data', {
|
|
1195
1195
|
schema: {
|
|
1196
1196
|
id: { type: 'id' },
|
|
1197
|
-
base_value: {
|
|
1197
|
+
base_value: {
|
|
1198
1198
|
type: 'number',
|
|
1199
1199
|
setter: (value) => value || 0
|
|
1200
1200
|
},
|
|
1201
|
-
multiplier: {
|
|
1201
|
+
multiplier: {
|
|
1202
1202
|
type: 'number',
|
|
1203
1203
|
setter: (value) => value || 1
|
|
1204
1204
|
},
|
|
1205
|
-
adjustment: {
|
|
1205
|
+
adjustment: {
|
|
1206
1206
|
type: 'number',
|
|
1207
1207
|
setter: (value) => value || 0
|
|
1208
1208
|
},
|
|
@@ -1210,7 +1210,7 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1210
1210
|
type: 'number',
|
|
1211
1211
|
setter: (value, { attributes }) => {
|
|
1212
1212
|
// Calculate based on other fields
|
|
1213
|
-
return (attributes.base_value || 0) * (attributes.multiplier || 1)
|
|
1213
|
+
return (attributes.base_value || 0) * (attributes.multiplier || 1)
|
|
1214
1214
|
},
|
|
1215
1215
|
runSetterAfter: ['base_value', 'multiplier']
|
|
1216
1216
|
},
|
|
@@ -1218,14 +1218,14 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1218
1218
|
type: 'number',
|
|
1219
1219
|
setter: (value, { attributes }) => {
|
|
1220
1220
|
// Depends on calculated_value
|
|
1221
|
-
return (attributes.calculated_value || 0) + (attributes.adjustment || 0)
|
|
1221
|
+
return (attributes.calculated_value || 0) + (attributes.adjustment || 0)
|
|
1222
1222
|
},
|
|
1223
1223
|
runSetterAfter: ['calculated_value', 'adjustment']
|
|
1224
1224
|
}
|
|
1225
1225
|
},
|
|
1226
1226
|
tableName: 'setter_computed'
|
|
1227
|
-
})
|
|
1228
|
-
await api.resources.computed_data.createKnexTable()
|
|
1227
|
+
})
|
|
1228
|
+
await api.resources.computed_data.createKnexTable()
|
|
1229
1229
|
|
|
1230
1230
|
// Nullable fields handling
|
|
1231
1231
|
await api.addResource('nullable_data', {
|
|
@@ -1251,8 +1251,8 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1251
1251
|
}
|
|
1252
1252
|
},
|
|
1253
1253
|
tableName: 'setter_nullable'
|
|
1254
|
-
})
|
|
1255
|
-
await api.resources.nullable_data.createKnexTable()
|
|
1254
|
+
})
|
|
1255
|
+
await api.resources.nullable_data.createKnexTable()
|
|
1256
1256
|
|
|
1257
1257
|
// Validated data
|
|
1258
1258
|
await api.addResource('validated_data', {
|
|
@@ -1278,27 +1278,27 @@ export async function createFieldSettersApi(knex, pluginOptions = {}) {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
},
|
|
1280
1280
|
tableName: 'setter_validated'
|
|
1281
|
-
})
|
|
1282
|
-
await api.resources.validated_data.createKnexTable()
|
|
1281
|
+
})
|
|
1282
|
+
await api.resources.validated_data.createKnexTable()
|
|
1283
1283
|
|
|
1284
1284
|
if (storageMode.isAnyApi()) {
|
|
1285
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
1285
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
1286
1286
|
}
|
|
1287
1287
|
|
|
1288
|
-
return api
|
|
1288
|
+
return api
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
1291
|
-
export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
1292
|
-
const { MultiHomePlugin } = await import('../../plugins/core/rest-api-multihome-plugin.js')
|
|
1293
|
-
|
|
1291
|
+
export async function createMultiHomeApi (knex, pluginOptions = {}) {
|
|
1292
|
+
const { MultiHomePlugin } = await import('../../plugins/core/rest-api-multihome-plugin.js')
|
|
1293
|
+
|
|
1294
1294
|
const api = new Api({
|
|
1295
1295
|
name: 'multihome-test-api',
|
|
1296
|
-
})
|
|
1296
|
+
})
|
|
1297
1297
|
|
|
1298
|
-
const tenantId = storageMode.isAnyApi() ? 'multihome_tenant' : storageMode.defaultTenant
|
|
1299
|
-
const previousTenant = storageMode.currentTenant
|
|
1298
|
+
const tenantId = storageMode.isAnyApi() ? 'multihome_tenant' : storageMode.defaultTenant
|
|
1299
|
+
const previousTenant = storageMode.currentTenant
|
|
1300
1300
|
if (storageMode.isAnyApi()) {
|
|
1301
|
-
storageMode.setCurrentTenant(tenantId)
|
|
1301
|
+
storageMode.setCurrentTenant(tenantId)
|
|
1302
1302
|
}
|
|
1303
1303
|
|
|
1304
1304
|
await api.use(RestApiPlugin, {
|
|
@@ -1315,16 +1315,16 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1315
1315
|
patch: 'minimal'
|
|
1316
1316
|
},
|
|
1317
1317
|
sortableFields: ['id', 'title', 'name', 'tenant_id']
|
|
1318
|
-
})
|
|
1319
|
-
|
|
1320
|
-
await useStoragePlugin(api, knex, { tenantId })
|
|
1321
|
-
await resetAnyApiTables(knex)
|
|
1322
|
-
|
|
1318
|
+
})
|
|
1319
|
+
|
|
1320
|
+
await useStoragePlugin(api, knex, { tenantId })
|
|
1321
|
+
await resetAnyApiTables(knex)
|
|
1322
|
+
|
|
1323
1323
|
// Add Express plugin if requested for transport testing
|
|
1324
1324
|
if (pluginOptions.includeExpress) {
|
|
1325
|
-
await api.use(ExpressPlugin, pluginOptions.express || {})
|
|
1325
|
+
await api.use(ExpressPlugin, pluginOptions.express || {})
|
|
1326
1326
|
}
|
|
1327
|
-
|
|
1327
|
+
|
|
1328
1328
|
// Add MultiHome plugin with configuration
|
|
1329
1329
|
await api.use(MultiHomePlugin, {
|
|
1330
1330
|
field: pluginOptions.field || 'tenant_id',
|
|
@@ -1333,9 +1333,9 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1333
1333
|
allowMissing: pluginOptions.allowMissing || false,
|
|
1334
1334
|
extractor: pluginOptions.extractor || ((request) => {
|
|
1335
1335
|
// Default to header extraction for tests
|
|
1336
|
-
return request.headers?.['x-tenant-id'] || null
|
|
1336
|
+
return request.headers?.['x-tenant-id'] || null
|
|
1337
1337
|
})
|
|
1338
|
-
})
|
|
1338
|
+
})
|
|
1339
1339
|
|
|
1340
1340
|
// Tenant-specific resources
|
|
1341
1341
|
await api.addResource('projects', {
|
|
@@ -1350,9 +1350,9 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1350
1350
|
tasks: { type: 'hasMany', target: 'tasks', foreignKey: 'project_id' }
|
|
1351
1351
|
},
|
|
1352
1352
|
tableName: 'multihome_projects'
|
|
1353
|
-
})
|
|
1354
|
-
await api.resources.projects.createKnexTable()
|
|
1355
|
-
mapTable('multihome_projects', 'projects')
|
|
1353
|
+
})
|
|
1354
|
+
await api.resources.projects.createKnexTable()
|
|
1355
|
+
mapTable('multihome_projects', 'projects')
|
|
1356
1356
|
|
|
1357
1357
|
await api.addResource('tasks', {
|
|
1358
1358
|
schema: {
|
|
@@ -1363,9 +1363,9 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1363
1363
|
tenant_id: { type: 'string', required: true }
|
|
1364
1364
|
},
|
|
1365
1365
|
tableName: 'multihome_tasks'
|
|
1366
|
-
})
|
|
1367
|
-
await api.resources.tasks.createKnexTable()
|
|
1368
|
-
mapTable('multihome_tasks', 'tasks')
|
|
1366
|
+
})
|
|
1367
|
+
await api.resources.tasks.createKnexTable()
|
|
1368
|
+
mapTable('multihome_tasks', 'tasks')
|
|
1369
1369
|
|
|
1370
1370
|
await api.addResource('users', {
|
|
1371
1371
|
schema: {
|
|
@@ -1376,9 +1376,9 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1376
1376
|
tenant_id: { type: 'string', required: true }
|
|
1377
1377
|
},
|
|
1378
1378
|
tableName: 'multihome_users'
|
|
1379
|
-
})
|
|
1380
|
-
await api.resources.users.createKnexTable()
|
|
1381
|
-
mapTable('multihome_users', 'users')
|
|
1379
|
+
})
|
|
1380
|
+
await api.resources.users.createKnexTable()
|
|
1381
|
+
mapTable('multihome_users', 'users')
|
|
1382
1382
|
|
|
1383
1383
|
// Global resource (excluded from multihome)
|
|
1384
1384
|
await api.addResource('system_settings', {
|
|
@@ -1388,26 +1388,26 @@ export async function createMultiHomeApi(knex, pluginOptions = {}) {
|
|
|
1388
1388
|
value: { type: 'string', required: true }
|
|
1389
1389
|
},
|
|
1390
1390
|
tableName: 'multihome_system_settings'
|
|
1391
|
-
})
|
|
1392
|
-
await api.resources.system_settings.createKnexTable()
|
|
1393
|
-
mapTable('multihome_system_settings', 'system_settings')
|
|
1391
|
+
})
|
|
1392
|
+
await api.resources.system_settings.createKnexTable()
|
|
1393
|
+
mapTable('multihome_system_settings', 'system_settings')
|
|
1394
1394
|
|
|
1395
1395
|
if (storageMode.isAnyApi()) {
|
|
1396
|
-
storageMode.setCurrentTenant(previousTenant)
|
|
1396
|
+
storageMode.setCurrentTenant(previousTenant)
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
|
-
return api
|
|
1399
|
+
return api
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
1402
|
/**
|
|
1403
1403
|
* Creates an API with positioning support for testing
|
|
1404
1404
|
*/
|
|
1405
|
-
export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
1406
|
-
const apiName = pluginOptions.apiName || 'positioning-test-api'
|
|
1407
|
-
const tablePrefix = pluginOptions.tablePrefix || 'positioning'
|
|
1405
|
+
export async function createPositioningApi (knex, pluginOptions = {}) {
|
|
1406
|
+
const apiName = pluginOptions.apiName || 'positioning-test-api'
|
|
1407
|
+
const tablePrefix = pluginOptions.tablePrefix || 'positioning'
|
|
1408
1408
|
const api = new Api({
|
|
1409
1409
|
name: apiName,
|
|
1410
|
-
})
|
|
1410
|
+
})
|
|
1411
1411
|
|
|
1412
1412
|
const restApiOptions = {
|
|
1413
1413
|
simplifiedApi: true, // Changed to true to allow simplified API calls in tests
|
|
@@ -1424,11 +1424,11 @@ export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
|
1424
1424
|
},
|
|
1425
1425
|
sortableFields: ['id', 'title', 'name', 'position', 'sort_order', 'category_id', 'project_id', 'status'],
|
|
1426
1426
|
...pluginOptions['rest-api']
|
|
1427
|
-
}
|
|
1427
|
+
}
|
|
1428
1428
|
|
|
1429
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
1430
|
-
await useStoragePlugin(api, knex)
|
|
1431
|
-
await resetAnyApiTables(knex)
|
|
1429
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
1430
|
+
await useStoragePlugin(api, knex)
|
|
1431
|
+
await resetAnyApiTables(knex)
|
|
1432
1432
|
|
|
1433
1433
|
// Categories (for grouping tasks)
|
|
1434
1434
|
await api.addResource('categories', {
|
|
@@ -1437,9 +1437,9 @@ export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
|
1437
1437
|
name: { type: 'string', required: true, max: 100 }
|
|
1438
1438
|
},
|
|
1439
1439
|
tableName: `${tablePrefix}_categories`
|
|
1440
|
-
})
|
|
1441
|
-
await api.resources.categories.createKnexTable()
|
|
1442
|
-
mapTable(`${tablePrefix}_categories`, 'categories')
|
|
1440
|
+
})
|
|
1441
|
+
await api.resources.categories.createKnexTable()
|
|
1442
|
+
mapTable(`${tablePrefix}_categories`, 'categories')
|
|
1443
1443
|
|
|
1444
1444
|
// Tasks (main positioning test resource)
|
|
1445
1445
|
await api.addResource('tasks', {
|
|
@@ -1456,9 +1456,9 @@ export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
|
1456
1456
|
category: { belongsTo: 'categories' }
|
|
1457
1457
|
},
|
|
1458
1458
|
tableName: `${tablePrefix}_tasks`
|
|
1459
|
-
})
|
|
1460
|
-
await api.resources.tasks.createKnexTable()
|
|
1461
|
-
mapTable(`${tablePrefix}_tasks`, 'tasks')
|
|
1459
|
+
})
|
|
1460
|
+
await api.resources.tasks.createKnexTable()
|
|
1461
|
+
mapTable(`${tablePrefix}_tasks`, 'tasks')
|
|
1462
1462
|
|
|
1463
1463
|
// Projects (for multi-filter testing)
|
|
1464
1464
|
await api.addResource('projects', {
|
|
@@ -1467,9 +1467,9 @@ export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
|
1467
1467
|
name: { type: 'string', required: true, max: 100 }
|
|
1468
1468
|
},
|
|
1469
1469
|
tableName: `${tablePrefix}_projects`
|
|
1470
|
-
})
|
|
1471
|
-
await api.resources.projects.createKnexTable()
|
|
1472
|
-
mapTable(`${tablePrefix}_projects`, 'projects')
|
|
1470
|
+
})
|
|
1471
|
+
await api.resources.projects.createKnexTable()
|
|
1472
|
+
mapTable(`${tablePrefix}_projects`, 'projects')
|
|
1473
1473
|
|
|
1474
1474
|
// Items (flexible resource for various positioning tests)
|
|
1475
1475
|
await api.addResource('items', {
|
|
@@ -1487,27 +1487,27 @@ export async function createPositioningApi(knex, pluginOptions = {}) {
|
|
|
1487
1487
|
project: { belongsTo: 'projects' }
|
|
1488
1488
|
},
|
|
1489
1489
|
tableName: `${tablePrefix}_items`
|
|
1490
|
-
})
|
|
1491
|
-
await api.resources.items.createKnexTable()
|
|
1492
|
-
mapTable(`${tablePrefix}_items`, 'items')
|
|
1490
|
+
})
|
|
1491
|
+
await api.resources.items.createKnexTable()
|
|
1492
|
+
mapTable(`${tablePrefix}_items`, 'items')
|
|
1493
1493
|
|
|
1494
|
-
return api
|
|
1494
|
+
return api
|
|
1495
1495
|
}
|
|
1496
1496
|
|
|
1497
1497
|
/**
|
|
1498
1498
|
* Creates an API with custom idProperty for all resources to test idProperty functionality
|
|
1499
1499
|
* Uses 'custom_id_' prefix for all tables to avoid conflicts
|
|
1500
1500
|
*/
|
|
1501
|
-
export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
1502
|
-
const apiName = pluginOptions.apiName ||
|
|
1503
|
-
const tablePrefix = pluginOptions.tablePrefix || 'custom_id'
|
|
1501
|
+
export async function createCustomIdPropertyApi (knex, pluginOptions = {}) {
|
|
1502
|
+
const apiName = pluginOptions.apiName || 'custom-id-test-api'
|
|
1503
|
+
const tablePrefix = pluginOptions.tablePrefix || 'custom_id'
|
|
1504
1504
|
const api = new Api({
|
|
1505
1505
|
name: apiName,
|
|
1506
1506
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
1507
|
-
})
|
|
1507
|
+
})
|
|
1508
1508
|
|
|
1509
1509
|
if (storageMode.isAnyApi()) {
|
|
1510
|
-
storageMode.clearRegistry()
|
|
1510
|
+
storageMode.clearRegistry()
|
|
1511
1511
|
}
|
|
1512
1512
|
|
|
1513
1513
|
const restApiOptions = {
|
|
@@ -1525,11 +1525,11 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1525
1525
|
},
|
|
1526
1526
|
sortableFields: ['country_id', 'publisher_id', 'author_id', 'book_id', 'title', 'name', 'code'],
|
|
1527
1527
|
...pluginOptions['rest-api'] // Merge any custom options for rest-api plugin
|
|
1528
|
-
}
|
|
1528
|
+
}
|
|
1529
1529
|
|
|
1530
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
1531
|
-
await useStoragePlugin(api, knex)
|
|
1532
|
-
await resetAnyApiTables(knex)
|
|
1530
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
1531
|
+
await useStoragePlugin(api, knex)
|
|
1532
|
+
await resetAnyApiTables(knex)
|
|
1533
1533
|
|
|
1534
1534
|
// Countries table with custom idProperty
|
|
1535
1535
|
await api.addResource('countries', {
|
|
@@ -1544,9 +1544,9 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1544
1544
|
},
|
|
1545
1545
|
tableName: `${tablePrefix}_countries`,
|
|
1546
1546
|
idProperty: 'country_id' // Custom ID property
|
|
1547
|
-
})
|
|
1548
|
-
await api.resources.countries.createKnexTable()
|
|
1549
|
-
mapTable(`${tablePrefix}_countries`, 'countries')
|
|
1547
|
+
})
|
|
1548
|
+
await api.resources.countries.createKnexTable()
|
|
1549
|
+
mapTable(`${tablePrefix}_countries`, 'countries')
|
|
1550
1550
|
|
|
1551
1551
|
// Publishers table with custom idProperty
|
|
1552
1552
|
await api.addResource('publishers', {
|
|
@@ -1556,17 +1556,17 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1556
1556
|
},
|
|
1557
1557
|
relationships: {
|
|
1558
1558
|
books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' },
|
|
1559
|
-
reviews: {
|
|
1560
|
-
type: 'hasMany',
|
|
1561
|
-
target: 'reviews',
|
|
1559
|
+
reviews: {
|
|
1560
|
+
type: 'hasMany',
|
|
1561
|
+
target: 'reviews',
|
|
1562
1562
|
via: 'reviewable'
|
|
1563
1563
|
}
|
|
1564
1564
|
},
|
|
1565
1565
|
tableName: `${tablePrefix}_publishers`,
|
|
1566
1566
|
idProperty: 'publisher_id'
|
|
1567
|
-
})
|
|
1568
|
-
await api.resources.publishers.createKnexTable()
|
|
1569
|
-
mapTable(`${tablePrefix}_publishers`, 'publishers')
|
|
1567
|
+
})
|
|
1568
|
+
await api.resources.publishers.createKnexTable()
|
|
1569
|
+
mapTable(`${tablePrefix}_publishers`, 'publishers')
|
|
1570
1570
|
|
|
1571
1571
|
// Authors table with custom idProperty
|
|
1572
1572
|
await api.addResource('authors', {
|
|
@@ -1577,17 +1577,17 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1577
1577
|
},
|
|
1578
1578
|
relationships: {
|
|
1579
1579
|
books: { type: 'manyToMany', through: 'book_authors', foreignKey: 'author_id', otherKey: 'book_id' },
|
|
1580
|
-
reviews: {
|
|
1581
|
-
type: 'hasMany',
|
|
1582
|
-
target: 'reviews',
|
|
1580
|
+
reviews: {
|
|
1581
|
+
type: 'hasMany',
|
|
1582
|
+
target: 'reviews',
|
|
1583
1583
|
via: 'reviewable'
|
|
1584
1584
|
}
|
|
1585
1585
|
},
|
|
1586
1586
|
tableName: `${tablePrefix}_authors`,
|
|
1587
1587
|
idProperty: 'author_id'
|
|
1588
|
-
})
|
|
1589
|
-
await api.resources.authors.createKnexTable()
|
|
1590
|
-
mapTable(`${tablePrefix}_authors`, 'authors')
|
|
1588
|
+
})
|
|
1589
|
+
await api.resources.authors.createKnexTable()
|
|
1590
|
+
mapTable(`${tablePrefix}_authors`, 'authors')
|
|
1591
1591
|
|
|
1592
1592
|
// Books table with custom idProperty
|
|
1593
1593
|
await api.addResource('books', {
|
|
@@ -1601,17 +1601,17 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1601
1601
|
},
|
|
1602
1602
|
relationships: {
|
|
1603
1603
|
authors: { type: 'manyToMany', through: 'book_authors', foreignKey: 'book_id', otherKey: 'author_id' },
|
|
1604
|
-
reviews: {
|
|
1605
|
-
type: 'hasMany',
|
|
1606
|
-
target: 'reviews',
|
|
1604
|
+
reviews: {
|
|
1605
|
+
type: 'hasMany',
|
|
1606
|
+
target: 'reviews',
|
|
1607
1607
|
via: 'reviewable'
|
|
1608
1608
|
}
|
|
1609
1609
|
},
|
|
1610
1610
|
tableName: `${tablePrefix}_books`,
|
|
1611
1611
|
idProperty: 'book_id'
|
|
1612
|
-
})
|
|
1613
|
-
await api.resources.books.createKnexTable()
|
|
1614
|
-
mapTable(`${tablePrefix}_books`, 'books')
|
|
1612
|
+
})
|
|
1613
|
+
await api.resources.books.createKnexTable()
|
|
1614
|
+
mapTable(`${tablePrefix}_books`, 'books')
|
|
1615
1615
|
|
|
1616
1616
|
// Book-Authors pivot table with custom idProperty
|
|
1617
1617
|
await api.addResource('book_authors', {
|
|
@@ -1623,9 +1623,9 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1623
1623
|
},
|
|
1624
1624
|
tableName: `${tablePrefix}_book_authors`,
|
|
1625
1625
|
idProperty: 'book_author_id'
|
|
1626
|
-
})
|
|
1627
|
-
await api.resources.book_authors.createKnexTable()
|
|
1628
|
-
mapTable(`${tablePrefix}_book_authors`, 'book_authors')
|
|
1626
|
+
})
|
|
1627
|
+
await api.resources.book_authors.createKnexTable()
|
|
1628
|
+
mapTable(`${tablePrefix}_book_authors`, 'book_authors')
|
|
1629
1629
|
|
|
1630
1630
|
// Polymorphic reviews with custom idProperty
|
|
1631
1631
|
await api.addResource('reviews', {
|
|
@@ -1650,36 +1650,36 @@ export async function createCustomIdPropertyApi(knex, pluginOptions = {}) {
|
|
|
1650
1650
|
relationships: {},
|
|
1651
1651
|
tableName: `${tablePrefix}_reviews`,
|
|
1652
1652
|
idProperty: 'review_id'
|
|
1653
|
-
})
|
|
1654
|
-
await api.resources.reviews.createKnexTable()
|
|
1655
|
-
mapTable(`${tablePrefix}_reviews`, 'reviews')
|
|
1653
|
+
})
|
|
1654
|
+
await api.resources.reviews.createKnexTable()
|
|
1655
|
+
mapTable(`${tablePrefix}_reviews`, 'reviews')
|
|
1656
1656
|
|
|
1657
1657
|
if (storageMode.isAnyApi()) {
|
|
1658
|
-
const tenant = storageMode.defaultTenant || 'default'
|
|
1659
|
-
const booksDescriptor = await api.anyapi.registry.getDescriptor(tenant, 'books')
|
|
1660
|
-
const authorsDescriptor = await api.anyapi.registry.getDescriptor(tenant, 'authors')
|
|
1661
|
-
const relationshipKey = booksDescriptor?.manyToMany?.authors?.relationship
|
|
1662
|
-
const inverseRelationshipKey = authorsDescriptor?.manyToMany?.books?.relationship
|
|
1658
|
+
const tenant = storageMode.defaultTenant || 'default'
|
|
1659
|
+
const booksDescriptor = await api.anyapi.registry.getDescriptor(tenant, 'books')
|
|
1660
|
+
const authorsDescriptor = await api.anyapi.registry.getDescriptor(tenant, 'authors')
|
|
1661
|
+
const relationshipKey = booksDescriptor?.manyToMany?.authors?.relationship
|
|
1662
|
+
const inverseRelationshipKey = authorsDescriptor?.manyToMany?.books?.relationship
|
|
1663
1663
|
storageMode.registerLink(
|
|
1664
1664
|
`${tablePrefix}_book_authors`,
|
|
1665
1665
|
'books',
|
|
1666
1666
|
'authors',
|
|
1667
1667
|
relationshipKey,
|
|
1668
|
-
inverseRelationshipKey
|
|
1669
|
-
)
|
|
1668
|
+
inverseRelationshipKey
|
|
1669
|
+
)
|
|
1670
1670
|
}
|
|
1671
1671
|
|
|
1672
|
-
return api
|
|
1672
|
+
return api
|
|
1673
1673
|
}
|
|
1674
1674
|
|
|
1675
1675
|
/**
|
|
1676
1676
|
* Creates an API configuration for multi-field cursor pagination testing
|
|
1677
1677
|
* Uses 'cursor_' prefix for all tables to avoid conflicts
|
|
1678
1678
|
*/
|
|
1679
|
-
export async function createCursorPaginationApi(knex) {
|
|
1679
|
+
export async function createCursorPaginationApi (knex) {
|
|
1680
1680
|
const api = new Api({
|
|
1681
1681
|
name: 'cursor-pagination-test-api',
|
|
1682
|
-
})
|
|
1682
|
+
})
|
|
1683
1683
|
|
|
1684
1684
|
const restApiOptions = {
|
|
1685
1685
|
simplified: false,
|
|
@@ -1691,12 +1691,12 @@ export async function createCursorPaginationApi(knex) {
|
|
|
1691
1691
|
sortableFields: ['id', 'name', 'category', 'brand', 'price', 'sku', 'created_at', 'code', 'status', 'type'],
|
|
1692
1692
|
queryDefaultLimit: 20,
|
|
1693
1693
|
queryMaxLimit: 100,
|
|
1694
|
-
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
1698
|
-
await useStoragePlugin(api, knex)
|
|
1699
|
-
await resetAnyApiTables(knex)
|
|
1694
|
+
returnBasePath: 'https://api.example.com'
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
1698
|
+
await useStoragePlugin(api, knex)
|
|
1699
|
+
await resetAnyApiTables(knex)
|
|
1700
1700
|
|
|
1701
1701
|
// Create products table with fields suitable for multi-field sorting
|
|
1702
1702
|
await api.addResource('products', {
|
|
@@ -1711,8 +1711,8 @@ export async function createCursorPaginationApi(knex) {
|
|
|
1711
1711
|
created_at: { type: 'dateTime', default: 'now()' }
|
|
1712
1712
|
},
|
|
1713
1713
|
tableName: 'cursor_products'
|
|
1714
|
-
})
|
|
1715
|
-
await api.resources.products.createKnexTable()
|
|
1714
|
+
})
|
|
1715
|
+
await api.resources.products.createKnexTable()
|
|
1716
1716
|
|
|
1717
1717
|
// Create items table with custom ID property
|
|
1718
1718
|
await api.addResource('items', {
|
|
@@ -1725,21 +1725,21 @@ export async function createCursorPaginationApi(knex) {
|
|
|
1725
1725
|
},
|
|
1726
1726
|
idProperty: 'item_id',
|
|
1727
1727
|
tableName: 'cursor_items'
|
|
1728
|
-
})
|
|
1729
|
-
await api.resources.items.createKnexTable()
|
|
1728
|
+
})
|
|
1729
|
+
await api.resources.items.createKnexTable()
|
|
1730
1730
|
|
|
1731
|
-
return api
|
|
1731
|
+
return api
|
|
1732
1732
|
}
|
|
1733
1733
|
|
|
1734
1734
|
/**
|
|
1735
1735
|
* Creates an API configuration for testing virtual fields validation
|
|
1736
1736
|
*/
|
|
1737
|
-
export async function createVirtualFieldsApi(knex, pluginOptions = {}) {
|
|
1738
|
-
const apiName = pluginOptions.apiName || 'virtual-fields-test-api'
|
|
1737
|
+
export async function createVirtualFieldsApi (knex, pluginOptions = {}) {
|
|
1738
|
+
const apiName = pluginOptions.apiName || 'virtual-fields-test-api'
|
|
1739
1739
|
const api = new Api({
|
|
1740
1740
|
name: apiName,
|
|
1741
1741
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
1742
|
-
})
|
|
1742
|
+
})
|
|
1743
1743
|
|
|
1744
1744
|
const restApiOptions = {
|
|
1745
1745
|
simplifiedApi: false,
|
|
@@ -1755,11 +1755,11 @@ export async function createVirtualFieldsApi(knex, pluginOptions = {}) {
|
|
|
1755
1755
|
patch: 'minimal'
|
|
1756
1756
|
},
|
|
1757
1757
|
...pluginOptions['rest-api']
|
|
1758
|
-
}
|
|
1758
|
+
}
|
|
1759
1759
|
|
|
1760
|
-
await api.use(RestApiPlugin, restApiOptions)
|
|
1761
|
-
await useStoragePlugin(api, knex)
|
|
1762
|
-
await resetAnyApiTables(knex)
|
|
1760
|
+
await api.use(RestApiPlugin, restApiOptions)
|
|
1761
|
+
await useStoragePlugin(api, knex)
|
|
1762
|
+
await resetAnyApiTables(knex)
|
|
1763
1763
|
|
|
1764
1764
|
// Add a test resource with virtual fields
|
|
1765
1765
|
await api.addResource('users', {
|
|
@@ -1768,7 +1768,7 @@ export async function createVirtualFieldsApi(knex, pluginOptions = {}) {
|
|
|
1768
1768
|
username: { type: 'string', required: true },
|
|
1769
1769
|
email: { type: 'string', required: true },
|
|
1770
1770
|
age: { type: 'number' },
|
|
1771
|
-
|
|
1771
|
+
|
|
1772
1772
|
// Virtual fields with validation rules
|
|
1773
1773
|
password: { type: 'string', virtual: true, minLength: 8 },
|
|
1774
1774
|
passwordConfirmation: { type: 'string', virtual: true, minLength: 8 },
|
|
@@ -1778,21 +1778,21 @@ export async function createVirtualFieldsApi(knex, pluginOptions = {}) {
|
|
|
1778
1778
|
metadata: { type: 'object', virtual: true }
|
|
1779
1779
|
},
|
|
1780
1780
|
tableName: 'virtual_users'
|
|
1781
|
-
})
|
|
1782
|
-
|
|
1783
|
-
await api.resources.users.createKnexTable()
|
|
1781
|
+
})
|
|
1782
|
+
|
|
1783
|
+
await api.resources.users.createKnexTable()
|
|
1784
1784
|
|
|
1785
|
-
return api
|
|
1785
|
+
return api
|
|
1786
1786
|
}
|
|
1787
1787
|
|
|
1788
1788
|
/**
|
|
1789
1789
|
* Creates an API configuration for testing searchSchema merge behavior
|
|
1790
1790
|
*/
|
|
1791
|
-
export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
1791
|
+
export async function createSearchSchemaMergeApi (knex, pluginOptions = {}) {
|
|
1792
1792
|
const api = new Api({
|
|
1793
1793
|
name: 'searchschema-merge-test-api',
|
|
1794
1794
|
log: { level: process.env.LOG_LEVEL || 'info' }
|
|
1795
|
-
})
|
|
1795
|
+
})
|
|
1796
1796
|
|
|
1797
1797
|
await api.use(RestApiPlugin, {
|
|
1798
1798
|
simplifiedApi: false,
|
|
@@ -1803,10 +1803,10 @@ export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
|
1803
1803
|
patch: false
|
|
1804
1804
|
},
|
|
1805
1805
|
...pluginOptions['rest-api']
|
|
1806
|
-
})
|
|
1807
|
-
|
|
1808
|
-
await useStoragePlugin(api, knex)
|
|
1809
|
-
await resetAnyApiTables(knex)
|
|
1806
|
+
})
|
|
1807
|
+
|
|
1808
|
+
await useStoragePlugin(api, knex)
|
|
1809
|
+
await resetAnyApiTables(knex)
|
|
1810
1810
|
|
|
1811
1811
|
// Test resource with both search:true and searchSchema
|
|
1812
1812
|
await api.addResource('products', {
|
|
@@ -1821,7 +1821,7 @@ export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
|
1821
1821
|
searchSchema: {
|
|
1822
1822
|
// This should override the price field from search:true
|
|
1823
1823
|
// 'between' operator requires array type for validation [min, max]
|
|
1824
|
-
price: {
|
|
1824
|
+
price: {
|
|
1825
1825
|
type: 'array',
|
|
1826
1826
|
filterOperator: 'between'
|
|
1827
1827
|
},
|
|
@@ -1839,8 +1839,8 @@ export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
|
1839
1839
|
}
|
|
1840
1840
|
},
|
|
1841
1841
|
tableName: 'searchmerge_products'
|
|
1842
|
-
})
|
|
1843
|
-
await api.resources.products.createKnexTable()
|
|
1842
|
+
})
|
|
1843
|
+
await api.resources.products.createKnexTable()
|
|
1844
1844
|
|
|
1845
1845
|
// Test resource with only search:true fields
|
|
1846
1846
|
await api.addResource('users', {
|
|
@@ -1851,8 +1851,8 @@ export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
|
1851
1851
|
bio: { type: 'string', search: { filterOperator: 'like' } }
|
|
1852
1852
|
},
|
|
1853
1853
|
tableName: 'searchmerge_users'
|
|
1854
|
-
})
|
|
1855
|
-
await api.resources.users.createKnexTable()
|
|
1854
|
+
})
|
|
1855
|
+
await api.resources.users.createKnexTable()
|
|
1856
1856
|
|
|
1857
1857
|
// Test resource with only explicit searchSchema
|
|
1858
1858
|
await api.addResource('orders', {
|
|
@@ -1866,9 +1866,9 @@ export async function createSearchSchemaMergeApi(knex, pluginOptions = {}) {
|
|
|
1866
1866
|
status: { type: 'array', filterOperator: 'in' } // 'in' operator requires array type
|
|
1867
1867
|
},
|
|
1868
1868
|
tableName: 'searchmerge_orders'
|
|
1869
|
-
})
|
|
1870
|
-
await api.resources.orders.createKnexTable()
|
|
1871
|
-
mapTable('searchmerge_orders', 'orders')
|
|
1869
|
+
})
|
|
1870
|
+
await api.resources.orders.createKnexTable()
|
|
1871
|
+
mapTable('searchmerge_orders', 'orders')
|
|
1872
1872
|
|
|
1873
|
-
return api
|
|
1873
|
+
return api
|
|
1874
1874
|
}
|