@teleporthq/teleport-plugin-next-data-source 0.43.62 → 0.43.64
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/__tests__/blog-post-categories.test.ts +131 -0
- package/__tests__/custom-page-transform.test.ts +119 -0
- package/__tests__/ecommerce-product-ratings.test.ts +155 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +147 -0
- package/__tests__/filter-groups.test.ts +174 -0
- package/__tests__/product-reviews-structured-data.test.ts +196 -0
- package/dist/cjs/data-source-fetchers.d.ts +3 -3
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
- package/dist/cjs/fetchers/csv-file.js +3 -3
- package/dist/cjs/fetchers/csv-file.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/cjs/fetchers/google-sheets.js +1 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +1 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +1 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redis.d.ts.map +1 -1
- package/dist/cjs/fetchers/redis.js +1 -1
- package/dist/cjs/fetchers/redis.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +2 -2
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +2 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +1 -1
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts +2 -2
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +1 -1
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/transformations/blog-post.d.ts +7 -1
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -2
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/category-taxonomy.d.ts +33 -0
- package/dist/cjs/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/cjs/transformations/category-taxonomy.js +42 -0
- package/dist/cjs/transformations/category-taxonomy.js.map +1 -0
- package/dist/cjs/transformations/custom-page.d.ts +18 -0
- package/dist/cjs/transformations/custom-page.d.ts.map +1 -0
- package/dist/cjs/transformations/custom-page.js +24 -0
- package/dist/cjs/transformations/custom-page.js.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +2 -35
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +23 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +38 -8
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
- package/dist/cjs/transformations/shared-utils.js +1 -1
- package/dist/cjs/transformations/shared-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +35 -3
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +116 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +3 -3
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +2 -2
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +2 -2
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
- package/dist/esm/fetchers/csv-file.js +4 -4
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +2 -2
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +2 -2
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +2 -2
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +2 -2
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +2 -2
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +2 -2
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +2 -2
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redis.d.ts.map +1 -1
- package/dist/esm/fetchers/redis.js +2 -2
- package/dist/esm/fetchers/redis.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +3 -3
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -3
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +2 -2
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts +2 -2
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +2 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +2 -2
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +1 -1
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/transformations/blog-post.d.ts +7 -1
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -2
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/category-taxonomy.d.ts +33 -0
- package/dist/esm/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/esm/transformations/category-taxonomy.js +37 -0
- package/dist/esm/transformations/category-taxonomy.js.map +1 -0
- package/dist/esm/transformations/custom-page.d.ts +18 -0
- package/dist/esm/transformations/custom-page.d.ts.map +1 -0
- package/dist/esm/transformations/custom-page.js +20 -0
- package/dist/esm/transformations/custom-page.js.map +1 -0
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +3 -36
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +23 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +37 -7
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
- package/dist/esm/transformations/shared-utils.js +1 -1
- package/dist/esm/transformations/shared-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +35 -3
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +112 -17
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/data-source-fetchers.ts +3 -3
- package/src/fetchers/airtable.ts +35 -36
- package/src/fetchers/clickhouse.ts +26 -36
- package/src/fetchers/csv-file.ts +13 -21
- package/src/fetchers/firestore.ts +41 -35
- package/src/fetchers/google-sheets.ts +15 -23
- package/src/fetchers/javascript.ts +11 -21
- package/src/fetchers/mariadb.ts +33 -44
- package/src/fetchers/mongodb.ts +66 -81
- package/src/fetchers/mysql.ts +33 -45
- package/src/fetchers/postgresql.ts +52 -62
- package/src/fetchers/redis.ts +6 -5
- package/src/fetchers/redshift.ts +29 -41
- package/src/fetchers/rest-api.ts +11 -21
- package/src/fetchers/supabase.ts +104 -41
- package/src/fetchers/teleport.ts +55 -66
- package/src/fetchers/turso.ts +27 -39
- package/src/index.ts +1 -1
- package/src/pagination-plugin.ts +3 -6
- package/src/transformations/blog-post.ts +28 -9
- package/src/transformations/category-taxonomy.ts +107 -0
- package/src/transformations/custom-page.ts +83 -0
- package/src/transformations/ecommerce-product.ts +177 -78
- package/src/transformations/index.ts +85 -11
- package/src/transformations/shared-utils.ts +9 -0
- package/src/utils.ts +191 -34
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateSortFilterHelperCode,
|
|
3
|
+
generateSafeJSONParseCode,
|
|
4
|
+
generateFilterTreeHelpersCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
import { generateHeaderDetectionCode } from './utils/header-detection'
|
|
3
7
|
|
|
4
8
|
export const validateGoogleSheetsConfig = (
|
|
@@ -53,6 +57,8 @@ ${generateSafeJSONParseCode()}
|
|
|
53
57
|
|
|
54
58
|
${generateSortFilterHelperCode()}
|
|
55
59
|
|
|
60
|
+
${generateFilterTreeHelpersCode()}
|
|
61
|
+
|
|
56
62
|
${generateHeaderDetectionCode()}
|
|
57
63
|
|
|
58
64
|
export default async function handler(req, res) {
|
|
@@ -280,18 +286,16 @@ export default async function handler(req, res) {
|
|
|
280
286
|
}
|
|
281
287
|
|
|
282
288
|
if (filters) {
|
|
283
|
-
const
|
|
289
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
284
290
|
|
|
285
|
-
if (
|
|
286
|
-
filteredData = filteredData.filter((item) =>
|
|
287
|
-
|
|
288
|
-
if (!filter.source || filter.destination === undefined) return true
|
|
289
|
-
|
|
291
|
+
if (filterTree) {
|
|
292
|
+
filteredData = filteredData.filter((item) =>
|
|
293
|
+
evaluateFilterTree(filterTree, (condition) => {
|
|
290
294
|
// Map label to column ID
|
|
291
|
-
const field = labelToIdMap[
|
|
295
|
+
const field = labelToIdMap[condition.source] || condition.source
|
|
292
296
|
const value = getNestedValue(item, field)
|
|
293
|
-
const target =
|
|
294
|
-
const operand =
|
|
297
|
+
const target = condition.destination
|
|
298
|
+
const operand = condition.operand
|
|
295
299
|
|
|
296
300
|
if (Array.isArray(target)) {
|
|
297
301
|
if (operand === '!=') {
|
|
@@ -302,19 +306,7 @@ export default async function handler(req, res) {
|
|
|
302
306
|
|
|
303
307
|
return compareValues(value, target, operand)
|
|
304
308
|
})
|
|
305
|
-
|
|
306
|
-
} else {
|
|
307
|
-
filteredData = filteredData.filter((item) => {
|
|
308
|
-
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
309
|
-
// Map label to column ID
|
|
310
|
-
const field = labelToIdMap[key] || key
|
|
311
|
-
const itemValue = getNestedValue(item, field)
|
|
312
|
-
if (Array.isArray(value)) {
|
|
313
|
-
return value.includes(itemValue)
|
|
314
|
-
}
|
|
315
|
-
return compareValues(itemValue, value, '=')
|
|
316
|
-
})
|
|
317
|
-
})
|
|
309
|
+
)
|
|
318
310
|
}
|
|
319
311
|
}
|
|
320
312
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
generateDateFormatterCode,
|
|
3
3
|
generateSortFilterHelperCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
export const validateJavaScriptConfig = (
|
|
@@ -47,6 +48,8 @@ ${generateDateFormatterCode()}
|
|
|
47
48
|
|
|
48
49
|
${generateSortFilterHelperCode()}
|
|
49
50
|
|
|
51
|
+
${generateFilterTreeHelpersCode()}
|
|
52
|
+
|
|
50
53
|
export default async function handler(req, res) {
|
|
51
54
|
try {
|
|
52
55
|
const { limit, offset, page, perPage, query, queryColumns, sortBy, sortOrder, filters, sorts } = req.query
|
|
@@ -87,17 +90,14 @@ export default async function handler(req, res) {
|
|
|
87
90
|
|
|
88
91
|
if (filters) {
|
|
89
92
|
try {
|
|
90
|
-
const
|
|
93
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
91
94
|
|
|
92
|
-
if (
|
|
93
|
-
data = data.filter((item) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const value = getNestedValue(item, field)
|
|
99
|
-
const target = filter.destination
|
|
100
|
-
const operand = filter.operand || '='
|
|
95
|
+
if (filterTree) {
|
|
96
|
+
data = data.filter((item) =>
|
|
97
|
+
evaluateFilterTree(filterTree, (condition) => {
|
|
98
|
+
const value = getNestedValue(item, condition.source)
|
|
99
|
+
const target = condition.destination
|
|
100
|
+
const operand = condition.operand
|
|
101
101
|
|
|
102
102
|
if (Array.isArray(target)) {
|
|
103
103
|
if (operand === '!=') {
|
|
@@ -108,17 +108,7 @@ export default async function handler(req, res) {
|
|
|
108
108
|
|
|
109
109
|
return compareValues(value, target, operand)
|
|
110
110
|
})
|
|
111
|
-
|
|
112
|
-
} else {
|
|
113
|
-
data = data.filter((item) => {
|
|
114
|
-
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
115
|
-
const itemValue = getNestedValue(item, key)
|
|
116
|
-
if (Array.isArray(value)) {
|
|
117
|
-
return value.includes(itemValue)
|
|
118
|
-
}
|
|
119
|
-
return compareValues(itemValue, value, '=')
|
|
120
|
-
})
|
|
121
|
-
})
|
|
111
|
+
)
|
|
122
112
|
}
|
|
123
113
|
} catch (err) {
|
|
124
114
|
console.error('Error parsing filters:', err)
|
package/src/fetchers/mariadb.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
generateSearchEscapeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
|
|
@@ -27,58 +28,46 @@ export const generateMariaDBFetcher = (
|
|
|
27
28
|
|
|
28
29
|
${generateSafeJSONParseCode()}
|
|
29
30
|
|
|
31
|
+
${generateFilterTreeHelpersCode()}
|
|
32
|
+
|
|
30
33
|
${generateSearchEscapeHelpersCode()}
|
|
31
34
|
|
|
32
35
|
// Helper function to process filters and build conditions
|
|
33
36
|
const processFilters = (filters, conditions, queryParams) => {
|
|
34
37
|
if (!filters) return
|
|
35
38
|
|
|
36
|
-
const
|
|
39
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
40
|
+
if (!filterTree) return
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
64
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
65
|
-
conditions.push(\`\${field} \${sqlOperator} ?\`)
|
|
66
|
-
queryParams.push(value)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
} else {
|
|
71
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
72
|
-
if (Array.isArray(value)) {
|
|
73
|
-
const placeholders = value.map(() => '?').join(', ')
|
|
74
|
-
queryParams.push(...value)
|
|
75
|
-
conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
|
|
76
|
-
} else {
|
|
77
|
-
conditions.push(\`\\\`\${key}\\\` = ?\`)
|
|
78
|
-
queryParams.push(value)
|
|
79
|
-
}
|
|
80
|
-
})
|
|
42
|
+
const buildCondition = (condition) => {
|
|
43
|
+
const field = \`\\\`\${condition.source}\\\`\`
|
|
44
|
+
const value = condition.destination
|
|
45
|
+
const operand = condition.operand
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
if (value.length === 0) return null
|
|
49
|
+
const placeholders = value.map(() => '?').join(', ')
|
|
50
|
+
queryParams.push(...value)
|
|
51
|
+
return operand === '!='
|
|
52
|
+
? \`\${field} NOT IN (\${placeholders})\`
|
|
53
|
+
: \`\${field} IN (\${placeholders})\`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (value === null) {
|
|
57
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
58
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate operator to prevent SQL injection
|
|
63
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
64
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
65
|
+
queryParams.push(value)
|
|
66
|
+
return \`\${field} \${sqlOperator} ?\`
|
|
81
67
|
}
|
|
68
|
+
|
|
69
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
70
|
+
if (clause) conditions.push(clause)
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
${generateDateFormatterCode()}
|
package/src/fetchers/mongodb.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
export const validateMongoDBConfig = (
|
|
@@ -73,95 +74,79 @@ export const generateMongoDBFetcher = (
|
|
|
73
74
|
|
|
74
75
|
${generateSafeJSONParseCode()}
|
|
75
76
|
|
|
77
|
+
${generateFilterTreeHelpersCode()}
|
|
78
|
+
|
|
76
79
|
// Helper function to process filters
|
|
77
80
|
const processFilters = (filters, filter) => {
|
|
78
81
|
if (!filters) return
|
|
79
82
|
|
|
80
|
-
const
|
|
83
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
84
|
+
if (!filterTree) return
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const value = filterItem.destination
|
|
88
|
-
const operand = filterItem.operand || '='
|
|
89
|
-
|
|
90
|
-
// Handle _id specially
|
|
91
|
-
const processValue = (v) => {
|
|
92
|
-
if (field === '_id' && typeof v === 'string') {
|
|
93
|
-
try {
|
|
94
|
-
return new ObjectId(v)
|
|
95
|
-
} catch (e) {
|
|
96
|
-
return v
|
|
97
|
-
}
|
|
98
|
-
}
|
|
86
|
+
const processValue = (field, v) => {
|
|
87
|
+
if (field === '_id' && typeof v === 'string') {
|
|
88
|
+
try {
|
|
89
|
+
return new ObjectId(v)
|
|
90
|
+
} catch (e) {
|
|
99
91
|
return v
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const processedValues = value.map(processValue)
|
|
104
|
-
if (operand === '!=') {
|
|
105
|
-
filter[field] = { $nin: processedValues }
|
|
106
|
-
} else {
|
|
107
|
-
filter[field] = { $in: processedValues }
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
const processedValue = processValue(value)
|
|
111
|
-
|
|
112
|
-
// Handle null values
|
|
113
|
-
if (processedValue === null) {
|
|
114
|
-
if (operand === '=') {
|
|
115
|
-
filter[field] = null
|
|
116
|
-
} else if (operand === '!=') {
|
|
117
|
-
filter[field] = { $ne: null }
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
// Map operand to MongoDB operators
|
|
121
|
-
switch (operand) {
|
|
122
|
-
case '=':
|
|
123
|
-
filter[field] = processedValue
|
|
124
|
-
break
|
|
125
|
-
case '!=':
|
|
126
|
-
filter[field] = { $ne: processedValue }
|
|
127
|
-
break
|
|
128
|
-
case '>':
|
|
129
|
-
filter[field] = { $gt: processedValue }
|
|
130
|
-
break
|
|
131
|
-
case '>=':
|
|
132
|
-
filter[field] = { $gte: processedValue }
|
|
133
|
-
break
|
|
134
|
-
case '<':
|
|
135
|
-
filter[field] = { $lt: processedValue }
|
|
136
|
-
break
|
|
137
|
-
case '<=':
|
|
138
|
-
filter[field] = { $lte: processedValue }
|
|
139
|
-
break
|
|
140
|
-
default:
|
|
141
|
-
filter[field] = processedValue
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
} else {
|
|
147
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
148
|
-
if (key === '_id') {
|
|
149
|
-
if (Array.isArray(value)) {
|
|
150
|
-
filter[key] = {
|
|
151
|
-
$in: value.map((id) => (typeof id === 'string' ? new ObjectId(id) : id))
|
|
152
|
-
}
|
|
153
|
-
} else if (typeof value === 'string') {
|
|
154
|
-
filter[key] = new ObjectId(value)
|
|
155
|
-
} else {
|
|
156
|
-
filter[key] = value
|
|
157
|
-
}
|
|
158
|
-
} else if (Array.isArray(value)) {
|
|
159
|
-
filter[key] = { $in: value }
|
|
160
|
-
} else {
|
|
161
|
-
filter[key] = value
|
|
162
|
-
}
|
|
163
|
-
})
|
|
93
|
+
}
|
|
94
|
+
return v
|
|
164
95
|
}
|
|
96
|
+
|
|
97
|
+
const buildCondition = (condition) => {
|
|
98
|
+
const field = condition.source
|
|
99
|
+
const value = condition.destination
|
|
100
|
+
const operand = condition.operand
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
const processedValues = value.map((entry) => processValue(field, entry))
|
|
104
|
+
return operand === '!='
|
|
105
|
+
? { [field]: { $nin: processedValues } }
|
|
106
|
+
: { [field]: { $in: processedValues } }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const processedValue = processValue(field, value)
|
|
110
|
+
|
|
111
|
+
if (processedValue === null) {
|
|
112
|
+
if (operand === '=') return { [field]: null }
|
|
113
|
+
if (operand === '!=') return { [field]: { $ne: null } }
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
switch (operand) {
|
|
118
|
+
case '!=':
|
|
119
|
+
return { [field]: { $ne: processedValue } }
|
|
120
|
+
case '>':
|
|
121
|
+
return { [field]: { $gt: processedValue } }
|
|
122
|
+
case '>=':
|
|
123
|
+
return { [field]: { $gte: processedValue } }
|
|
124
|
+
case '<':
|
|
125
|
+
return { [field]: { $lt: processedValue } }
|
|
126
|
+
case '<=':
|
|
127
|
+
return { [field]: { $lte: processedValue } }
|
|
128
|
+
default:
|
|
129
|
+
return { [field]: processedValue }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const buildNode = (node) => {
|
|
134
|
+
if (node.type === 'group') {
|
|
135
|
+
const parts = node.children.map(buildNode).filter(Boolean)
|
|
136
|
+
if (parts.length === 0) return null
|
|
137
|
+
if (parts.length === 1) return parts[0]
|
|
138
|
+
return node.operator === 'or' ? { $or: parts } : { $and: parts }
|
|
139
|
+
}
|
|
140
|
+
return buildCondition(node)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const query = buildNode(filterTree)
|
|
144
|
+
if (!query) return
|
|
145
|
+
|
|
146
|
+
// Merged under $and rather than assigned onto filter directly: the search
|
|
147
|
+
// above may already own filter.$or, and two conditions on the same field
|
|
148
|
+
// would otherwise overwrite each other.
|
|
149
|
+
filter.$and = (filter.$and || []).concat([query])
|
|
165
150
|
}
|
|
166
151
|
|
|
167
152
|
${generateDateFormatterCode()}
|
package/src/fetchers/mysql.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
generateSearchEscapeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
|
|
@@ -64,59 +65,46 @@ const getConnection = () => {
|
|
|
64
65
|
|
|
65
66
|
${generateSafeJSONParseCode()}
|
|
66
67
|
|
|
68
|
+
${generateFilterTreeHelpersCode()}
|
|
69
|
+
|
|
67
70
|
${generateSearchEscapeHelpersCode()}
|
|
68
71
|
|
|
69
72
|
// Helper function to process filters and build conditions
|
|
70
73
|
const processFilters = (filters, conditions, queryParams) => {
|
|
71
74
|
if (!filters) return
|
|
72
75
|
|
|
73
|
-
const
|
|
76
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
77
|
+
if (!filterTree) return
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Validate operator to prevent SQL injection
|
|
101
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
102
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
103
|
-
conditions.push(\`\${field} \${sqlOperator} ?\`)
|
|
104
|
-
queryParams.push(value)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
} else {
|
|
109
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
110
|
-
if (Array.isArray(value)) {
|
|
111
|
-
const placeholders = value.map(() => '?').join(', ')
|
|
112
|
-
queryParams.push(...value)
|
|
113
|
-
conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
|
|
114
|
-
} else {
|
|
115
|
-
conditions.push(\`\${mysql.escapeId(key)} = ?\`)
|
|
116
|
-
queryParams.push(value)
|
|
117
|
-
}
|
|
118
|
-
})
|
|
79
|
+
const buildCondition = (condition) => {
|
|
80
|
+
const field = mysql.escapeId(condition.source)
|
|
81
|
+
const value = condition.destination
|
|
82
|
+
const operand = condition.operand
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (value.length === 0) return null
|
|
86
|
+
const placeholders = value.map(() => '?').join(', ')
|
|
87
|
+
queryParams.push(...value)
|
|
88
|
+
return operand === '!='
|
|
89
|
+
? \`\${field} NOT IN (\${placeholders})\`
|
|
90
|
+
: \`\${field} IN (\${placeholders})\`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (value === null) {
|
|
94
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
95
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate operator to prevent SQL injection
|
|
100
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
101
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
102
|
+
queryParams.push(value)
|
|
103
|
+
return \`\${field} \${sqlOperator} ?\`
|
|
119
104
|
}
|
|
105
|
+
|
|
106
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
107
|
+
if (clause) conditions.push(clause)
|
|
120
108
|
}
|
|
121
109
|
|
|
122
110
|
${generateDateFormatterCode()}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
5
|
generateSearchEscapeHelpersCode,
|
|
6
|
+
generateFilterTreeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
import {
|
|
8
9
|
generateSortFallbackFieldHelper,
|
|
@@ -62,78 +63,67 @@ const getClient = () => {
|
|
|
62
63
|
|
|
63
64
|
${generateSafeJSONParseCode()}
|
|
64
65
|
|
|
66
|
+
${generateFilterTreeHelpersCode()}
|
|
67
|
+
|
|
65
68
|
${generateSearchEscapeHelpersCode()}
|
|
66
69
|
|
|
67
|
-
//
|
|
70
|
+
// Builds one SQL clause for the whole filter tree, so an OR group nested in
|
|
71
|
+
// the root AND keeps its meaning instead of being flattened into ANDs.
|
|
68
72
|
const processFilters = (filters, conditions, queryParams, paramIndex) => {
|
|
69
73
|
if (!filters) return paramIndex
|
|
70
74
|
|
|
71
|
-
const
|
|
75
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
76
|
+
if (!filterTree) return paramIndex
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (operand === 'array_overlap') {
|
|
84
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
85
|
-
queryParams.push(value.map((entry) => String(entry)))
|
|
86
|
-
paramIndex++
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
90
|
-
queryParams.push(...value)
|
|
91
|
-
if (operand === '!=') {
|
|
92
|
-
conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
|
|
93
|
-
} else {
|
|
94
|
-
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
if (operand === 'array_overlap') {
|
|
98
|
-
if (value === '' || value === null || value === undefined) return
|
|
99
|
-
// A single comma-joined string (the multi-select Category Filter's
|
|
100
|
-
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
101
|
-
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
102
|
-
if (overlapValues.length === 0) return
|
|
103
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
104
|
-
queryParams.push(overlapValues)
|
|
105
|
-
paramIndex++
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
if (value === null) {
|
|
109
|
-
if (operand === '=') {
|
|
110
|
-
conditions.push(\`\${field} IS NULL\`)
|
|
111
|
-
} else if (operand === '!=') {
|
|
112
|
-
conditions.push(\`\${field} IS NOT NULL\`)
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
116
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
117
|
-
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
118
|
-
queryParams.push(value)
|
|
119
|
-
paramIndex++
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
} else {
|
|
124
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
125
|
-
if (Array.isArray(value)) {
|
|
126
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
127
|
-
queryParams.push(...value)
|
|
128
|
-
conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
|
|
129
|
-
} else {
|
|
130
|
-
conditions.push(\`\${key} = $\${paramIndex}\`)
|
|
131
|
-
queryParams.push(value)
|
|
78
|
+
const buildCondition = (condition) => {
|
|
79
|
+
const field = condition.source
|
|
80
|
+
const value = condition.destination
|
|
81
|
+
const operand = condition.operand
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
if (value.length === 0) return null
|
|
85
|
+
if (operand === 'array_overlap') {
|
|
86
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
87
|
+
queryParams.push(value.map((entry) => String(entry)))
|
|
132
88
|
paramIndex++
|
|
89
|
+
return clause
|
|
133
90
|
}
|
|
134
|
-
|
|
91
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
92
|
+
queryParams.push(...value)
|
|
93
|
+
return operand === '!='
|
|
94
|
+
? \`\${field} NOT IN (\${placeholders.join(', ')})\`
|
|
95
|
+
: \`\${field} IN (\${placeholders.join(', ')})\`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (operand === 'array_overlap') {
|
|
99
|
+
if (value === '' || value === null || value === undefined) return null
|
|
100
|
+
// A single comma-joined string (the multi-select Category Filter's
|
|
101
|
+
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
102
|
+
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
103
|
+
if (overlapValues.length === 0) return null
|
|
104
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
105
|
+
queryParams.push(overlapValues)
|
|
106
|
+
paramIndex++
|
|
107
|
+
return clause
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (value === null) {
|
|
111
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
112
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
117
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
118
|
+
const clause = \`\${field} \${sqlOperator} $\${paramIndex}\`
|
|
119
|
+
queryParams.push(value)
|
|
120
|
+
paramIndex++
|
|
121
|
+
return clause
|
|
135
122
|
}
|
|
136
123
|
|
|
124
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
125
|
+
if (clause) conditions.push(clause)
|
|
126
|
+
|
|
137
127
|
return paramIndex
|
|
138
128
|
}
|
|
139
129
|
|
package/src/fetchers/redis.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
export const validateRedisConfig = (
|
|
@@ -69,6 +70,8 @@ export const generateRedisFetcher = (config: Record<string, unknown>): string =>
|
|
|
69
70
|
|
|
70
71
|
${generateSafeJSONParseCode()}
|
|
71
72
|
|
|
73
|
+
${generateFilterTreeHelpersCode()}
|
|
74
|
+
|
|
72
75
|
${generateDateFormatterCode()}
|
|
73
76
|
|
|
74
77
|
export default async function handler(req, res) {
|
|
@@ -88,14 +91,12 @@ export default async function handler(req, res) {
|
|
|
88
91
|
|
|
89
92
|
// Extract pattern from filters if available (new format)
|
|
90
93
|
if (filters) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
const patternFilter =
|
|
94
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
95
|
+
if (filterTree) {
|
|
96
|
+
const patternFilter = flattenFilterConditions(filterTree).find((f) => f.source === 'pattern')
|
|
94
97
|
if (patternFilter) {
|
|
95
98
|
pattern = patternFilter.destination || pattern
|
|
96
99
|
}
|
|
97
|
-
} else {
|
|
98
|
-
pattern = parsedFilters.pattern || pattern
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|