@teleporthq/teleport-plugin-next-data-source 0.42.8 → 0.42.10
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__/csv-header-detection.test.ts +212 -0
- package/__tests__/validation.test.ts +33 -2
- package/dist/cjs/data-source-fetchers.d.ts +2 -2
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +30 -7
- 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 +2 -2
- 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 +23 -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 +6 -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 +2 -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 +3 -3
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +11 -3
- 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 +2 -2
- 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 +3 -3
- 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 +3 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/static-collection.d.ts.map +1 -1
- package/dist/cjs/fetchers/static-collection.js +2 -1
- package/dist/cjs/fetchers/static-collection.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +2 -2
- package/dist/cjs/fetchers/supabase.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/fetchers/utils/header-detection.d.ts +2 -0
- package/dist/cjs/fetchers/utils/header-detection.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/header-detection.js +8 -0
- package/dist/cjs/fetchers/utils/header-detection.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +168 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +320 -65
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +218 -46
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +2 -2
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +29 -6
- 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 +3 -3
- 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 +23 -3
- 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 +6 -1
- 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 -1
- 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 +4 -4
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +12 -4
- 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 +3 -3
- 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 +4 -4
- 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 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/static-collection.d.ts.map +1 -1
- package/dist/esm/fetchers/static-collection.js +2 -1
- package/dist/esm/fetchers/static-collection.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +3 -3
- package/dist/esm/fetchers/supabase.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/fetchers/utils/header-detection.d.ts +2 -0
- package/dist/esm/fetchers/utils/header-detection.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/header-detection.js +4 -0
- package/dist/esm/fetchers/utils/header-detection.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +169 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +320 -65
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +214 -45
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/data-source-fetchers.ts +29 -13
- package/src/fetchers/airtable.ts +81 -31
- package/src/fetchers/clickhouse.ts +88 -19
- package/src/fetchers/csv-file.ts +256 -27
- package/src/fetchers/firestore.ts +65 -13
- package/src/fetchers/google-sheets.ts +243 -26
- package/src/fetchers/javascript.ts +106 -23
- package/src/fetchers/mariadb.ts +85 -26
- package/src/fetchers/mongodb.ts +156 -37
- package/src/fetchers/mysql.ts +86 -26
- package/src/fetchers/postgresql.ts +89 -27
- package/src/fetchers/redis.ts +43 -5
- package/src/fetchers/redshift.ts +87 -18
- package/src/fetchers/rest-api.ts +105 -24
- package/src/fetchers/static-collection.ts +100 -18
- package/src/fetchers/supabase.ts +178 -54
- package/src/fetchers/turso.ts +87 -18
- package/src/fetchers/utils/header-detection.ts +200 -0
- package/src/index.ts +248 -2
- package/src/pagination-plugin.ts +708 -191
- package/src/utils.ts +374 -38
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
export const validateFirestoreConfig = (
|
|
4
8
|
config: Record<string, unknown>
|
|
@@ -67,29 +71,67 @@ const getFirestore = () => {
|
|
|
67
71
|
return firestore
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
${generateSafeJSONParseCode()}
|
|
75
|
+
|
|
76
|
+
${generateDateFormatterCode()}
|
|
77
|
+
|
|
70
78
|
export default async function handler(req, res) {
|
|
71
79
|
try {
|
|
72
80
|
const firestore = getFirestore()
|
|
73
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
81
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
74
82
|
|
|
75
83
|
let queryRef = firestore.collection('${tableName}')
|
|
76
84
|
|
|
77
85
|
if (filters) {
|
|
78
|
-
const parsedFilters =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
const parsedFilters = safeJSONParse(filters)
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(parsedFilters)) {
|
|
89
|
+
parsedFilters.forEach((filter) => {
|
|
90
|
+
if (!filter.source || filter.destination === undefined) return
|
|
91
|
+
|
|
92
|
+
const field = filter.source
|
|
93
|
+
const value = filter.destination
|
|
94
|
+
const operand = filter.operand || '='
|
|
95
|
+
|
|
96
|
+
// Map operands to Firestore operators
|
|
97
|
+
const operatorMap = {
|
|
98
|
+
'=': '==',
|
|
99
|
+
'!=': '!=',
|
|
100
|
+
'>': '>',
|
|
101
|
+
'<': '<',
|
|
102
|
+
'>=': '>=',
|
|
103
|
+
'<=': '<=',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
if (value.length === 0) return
|
|
108
|
+
if (operand === '!=') {
|
|
109
|
+
queryRef = queryRef.where(field, 'not-in', value)
|
|
110
|
+
} else {
|
|
111
|
+
queryRef = queryRef.where(field, 'in', value)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
const firestoreOp = operatorMap[operand] || '=='
|
|
115
|
+
queryRef = queryRef.where(field, firestoreOp, value)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
queryRef = queryRef.where(key, 'in', value)
|
|
122
|
+
} else {
|
|
123
|
+
queryRef = queryRef.where(key, '==', value)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
let usePostFiltering = false
|
|
89
130
|
|
|
90
131
|
if (query) {
|
|
91
132
|
if (queryColumns) {
|
|
92
|
-
const
|
|
133
|
+
const parsed = safeJSONParse(queryColumns)
|
|
134
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
93
135
|
for (const column of columns) {
|
|
94
136
|
queryRef = queryRef
|
|
95
137
|
.where(column, '>=', query)
|
|
@@ -102,7 +144,17 @@ export default async function handler(req, res) {
|
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
|
|
105
|
-
|
|
147
|
+
// Handle sorts - new array format
|
|
148
|
+
if (sorts) {
|
|
149
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
150
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
151
|
+
parsedSorts.forEach((sort) => {
|
|
152
|
+
if (!sort.field) return
|
|
153
|
+
const order = sort.order?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
154
|
+
queryRef = queryRef.orderBy(sort.field, order)
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
} else if (sortBy) {
|
|
106
158
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
107
159
|
queryRef = queryRef.orderBy(sortBy, sortOrderValue)
|
|
108
160
|
}
|
|
@@ -150,7 +202,7 @@ export default async function handler(req, res) {
|
|
|
150
202
|
}
|
|
151
203
|
}
|
|
152
204
|
|
|
153
|
-
const safeData = JSON.parse(JSON.stringify(documents))
|
|
205
|
+
const safeData = JSON.parse(JSON.stringify(documents, dateReplacer))
|
|
154
206
|
|
|
155
207
|
return res.status(200).json({
|
|
156
208
|
success: true,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { generateSortFilterHelperCode, generateSafeJSONParseCode } from '../utils'
|
|
2
|
+
import { generateHeaderDetectionCode } from './utils/header-detection'
|
|
3
|
+
|
|
1
4
|
export const validateGoogleSheetsConfig = (
|
|
2
5
|
config: Record<string, unknown>
|
|
3
6
|
): { isValid: boolean; error?: string } => {
|
|
@@ -33,18 +36,33 @@ interface GoogleSheetsConfig {
|
|
|
33
36
|
sheetName?: string
|
|
34
37
|
range?: string
|
|
35
38
|
maxRows?: number
|
|
39
|
+
autoDetectHeader?: boolean
|
|
40
|
+
firstRowIsHeader?: boolean
|
|
41
|
+
columns?: Array<{ id: string; label?: string; type?: string }>
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
export const generateGoogleSheetsFetcher = (config: Record<string, unknown>): string => {
|
|
39
45
|
const sheetsConfig = config as GoogleSheetsConfig
|
|
46
|
+
const autoDetectHeader = sheetsConfig.autoDetectHeader
|
|
47
|
+
const firstRowIsHeader = sheetsConfig.firstRowIsHeader
|
|
48
|
+
const configColumns = sheetsConfig.columns || []
|
|
49
|
+
|
|
40
50
|
return `import fetch from 'node-fetch'
|
|
41
51
|
|
|
52
|
+
${generateSafeJSONParseCode()}
|
|
53
|
+
|
|
54
|
+
${generateSortFilterHelperCode()}
|
|
55
|
+
|
|
56
|
+
${generateHeaderDetectionCode()}
|
|
57
|
+
|
|
42
58
|
export default async function handler(req, res) {
|
|
43
59
|
try {
|
|
44
60
|
const sheetUrl = ${JSON.stringify(sheetsConfig.sheetUrl)}
|
|
45
61
|
let sheetId = ${JSON.stringify(sheetsConfig.sheetId)}
|
|
46
62
|
const range = ${JSON.stringify(sheetsConfig.range || 'A1:Z1000')}
|
|
47
63
|
const maxRows = ${sheetsConfig.maxRows || 0}
|
|
64
|
+
const autoDetectHeader = ${autoDetectHeader !== undefined ? autoDetectHeader : true}
|
|
65
|
+
const firstRowIsHeader = ${firstRowIsHeader !== undefined ? firstRowIsHeader : true}
|
|
48
66
|
|
|
49
67
|
if (!sheetId && sheetUrl) {
|
|
50
68
|
const match = sheetUrl.match(/\\/d\\/([a-zA-Z0-9-_]+)/)
|
|
@@ -59,8 +77,18 @@ export default async function handler(req, res) {
|
|
|
59
77
|
})
|
|
60
78
|
}
|
|
61
79
|
|
|
80
|
+
let gid = undefined
|
|
81
|
+
if (sheetUrl) {
|
|
82
|
+
const gidMatch = sheetUrl.match(/[#&]gid=([0-9]+)/)
|
|
83
|
+
gid = gidMatch ? gidMatch[1] : undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
let url = \`https://docs.google.com/spreadsheets/d/\${sheetId}/gviz/tq?tqx=out:json&range=\${range}\`
|
|
63
87
|
|
|
88
|
+
if (gid) {
|
|
89
|
+
url += \`&gid=\${gid}\`
|
|
90
|
+
}
|
|
91
|
+
|
|
64
92
|
if (maxRows && maxRows > 0) {
|
|
65
93
|
url += \`&tq=limit \${maxRows}\`
|
|
66
94
|
}
|
|
@@ -96,34 +124,146 @@ export default async function handler(req, res) {
|
|
|
96
124
|
})
|
|
97
125
|
}
|
|
98
126
|
|
|
127
|
+
const formatDateValue = (date) => {
|
|
128
|
+
const options = {
|
|
129
|
+
year: 'numeric',
|
|
130
|
+
month: 'short',
|
|
131
|
+
day: 'numeric',
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const timeOptions = {
|
|
135
|
+
hour: '2-digit',
|
|
136
|
+
minute: '2-digit',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const hasTime = date.getHours() !== 0 || date.getMinutes() !== 0 || date.getSeconds() !== 0
|
|
140
|
+
|
|
141
|
+
if (hasTime) {
|
|
142
|
+
return date.toLocaleString('en-US', { ...options, ...timeOptions })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return date.toLocaleDateString('en-US', options)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const parseGoogleSheetsValue = (value) => {
|
|
149
|
+
if (typeof value === 'string') {
|
|
150
|
+
const dateMatch = value.match(/^Date\\((\\d+),(\\d+),(\\d+)(?:,(\\d+),(\\d+),(\\d+))?\\)$/)
|
|
151
|
+
if (dateMatch) {
|
|
152
|
+
const year = parseInt(dateMatch[1], 10)
|
|
153
|
+
const month = parseInt(dateMatch[2], 10)
|
|
154
|
+
const day = parseInt(dateMatch[3], 10)
|
|
155
|
+
const hour = dateMatch[4] ? parseInt(dateMatch[4], 10) : 0
|
|
156
|
+
const minute = dateMatch[5] ? parseInt(dateMatch[5], 10) : 0
|
|
157
|
+
const second = dateMatch[6] ? parseInt(dateMatch[6], 10) : 0
|
|
158
|
+
const date = new Date(year, month, day, hour, minute, second)
|
|
159
|
+
return formatDateValue(date)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return value
|
|
163
|
+
}
|
|
164
|
+
|
|
99
165
|
const table = data.table
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
166
|
+
const rawRows = table.rows || []
|
|
167
|
+
|
|
168
|
+
if (rawRows.length === 0) {
|
|
169
|
+
return res.status(200).json({
|
|
170
|
+
success: true,
|
|
171
|
+
data: [],
|
|
172
|
+
timestamp: Date.now()
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const firstRow = rawRows[0]
|
|
177
|
+
const firstRowValues = firstRow.c.map((cell) => cell?.v ?? cell?.f ?? null)
|
|
178
|
+
|
|
179
|
+
const shouldAutoDetect = autoDetectHeader !== false
|
|
180
|
+
let hasHeaderRow = false
|
|
181
|
+
|
|
182
|
+
if (shouldAutoDetect) {
|
|
183
|
+
hasHeaderRow = detectHeaderRow(firstRowValues, rawRows)
|
|
184
|
+
} else {
|
|
185
|
+
hasHeaderRow = firstRowIsHeader !== false
|
|
186
|
+
}
|
|
105
187
|
|
|
106
|
-
const
|
|
188
|
+
const configColumns = ${JSON.stringify(configColumns)}
|
|
189
|
+
const useConfigColumns = configColumns && configColumns.length > 0
|
|
190
|
+
|
|
191
|
+
let columns
|
|
192
|
+
let dataRows
|
|
193
|
+
|
|
194
|
+
if (hasHeaderRow && rawRows.length > 1) {
|
|
195
|
+
columns = table.cols.map((col, index) => {
|
|
196
|
+
const configCol = useConfigColumns ? configColumns[index] : null
|
|
197
|
+
return {
|
|
198
|
+
id: configCol?.id || col.id || \`col_\${index}\`,
|
|
199
|
+
label: String(firstRowValues[index] || col.label || configCol?.label || \`Column \${index + 1}\`),
|
|
200
|
+
type: configCol?.type || col.type || 'string'
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
dataRows = rawRows.slice(1)
|
|
204
|
+
} else {
|
|
205
|
+
columns = table.cols.map((col, index) => {
|
|
206
|
+
const configCol = useConfigColumns ? configColumns[index] : null
|
|
207
|
+
return {
|
|
208
|
+
id: configCol?.id || col.id || \`col_\${index}\`,
|
|
209
|
+
label: configCol?.label || col.label || \`Column \${index + 1}\`,
|
|
210
|
+
type: configCol?.type || col.type || 'string'
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
dataRows = rawRows
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (maxRows && dataRows.length > maxRows) {
|
|
217
|
+
dataRows = dataRows.slice(0, maxRows)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const rows = dataRows.map((row) => {
|
|
107
221
|
const rowData = {}
|
|
108
222
|
row.c.forEach((cell, index) => {
|
|
109
|
-
const columnId = columns[index]
|
|
110
|
-
|
|
223
|
+
const columnId = columns[index]?.id || \`col_\${index}\`
|
|
224
|
+
const rawValue = cell?.v ?? cell?.f ?? null
|
|
225
|
+
rowData[columnId] = parseGoogleSheetsValue(rawValue)
|
|
111
226
|
})
|
|
112
227
|
return rowData
|
|
113
228
|
})
|
|
114
229
|
|
|
115
|
-
const
|
|
230
|
+
const columnsWithData = columns.filter((col, index) => {
|
|
231
|
+
const hasHeaderData = col.label && col.label !== \`Column \${index + 1}\`
|
|
232
|
+
const hasDataInColumn = rows.some((row) => {
|
|
233
|
+
const value = row[col.id]
|
|
234
|
+
return value !== null && value !== undefined && value !== ''
|
|
235
|
+
})
|
|
236
|
+
return hasHeaderData || hasDataInColumn
|
|
237
|
+
})
|
|
116
238
|
|
|
117
|
-
|
|
239
|
+
const filteredRows = rows.map((row) => {
|
|
240
|
+
const filteredRow = {}
|
|
241
|
+
columnsWithData.forEach((col) => {
|
|
242
|
+
filteredRow[col.id] = row[col.id]
|
|
243
|
+
})
|
|
244
|
+
return filteredRow
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query
|
|
248
|
+
|
|
249
|
+
// Create label-to-ID mapping for filters and sorts
|
|
250
|
+
const labelToIdMap = {}
|
|
251
|
+
columnsWithData.forEach((col) => {
|
|
252
|
+
labelToIdMap[col.label] = col.id
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
let filteredData = [...filteredRows]
|
|
118
256
|
|
|
119
257
|
if (query) {
|
|
120
258
|
const searchQuery = query.toLowerCase()
|
|
121
259
|
|
|
122
260
|
if (queryColumns) {
|
|
123
|
-
const searchColumns =
|
|
261
|
+
const searchColumns = safeJSONParse(queryColumns)
|
|
124
262
|
filteredData = filteredData.filter((item) => {
|
|
125
263
|
return searchColumns.some((col) => {
|
|
126
|
-
|
|
264
|
+
// Map label to column ID
|
|
265
|
+
const field = labelToIdMap[col] || col
|
|
266
|
+
const value = getNestedValue(item, field)
|
|
127
267
|
return value && String(value).toLowerCase().includes(searchQuery)
|
|
128
268
|
})
|
|
129
269
|
})
|
|
@@ -140,25 +280,102 @@ export default async function handler(req, res) {
|
|
|
140
280
|
}
|
|
141
281
|
|
|
142
282
|
if (filters) {
|
|
143
|
-
const parsedFilters =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
283
|
+
const parsedFilters = safeJSONParse(filters)
|
|
284
|
+
|
|
285
|
+
if (Array.isArray(parsedFilters)) {
|
|
286
|
+
filteredData = filteredData.filter((item) => {
|
|
287
|
+
return parsedFilters.every((filter) => {
|
|
288
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
289
|
+
|
|
290
|
+
// Map label to column ID
|
|
291
|
+
const field = labelToIdMap[filter.source] || filter.source
|
|
292
|
+
const value = getNestedValue(item, field)
|
|
293
|
+
const target = filter.destination
|
|
294
|
+
const operand = filter.operand || '='
|
|
295
|
+
|
|
296
|
+
if (Array.isArray(target)) {
|
|
297
|
+
if (operand === '!=') {
|
|
298
|
+
return !target.includes(value)
|
|
299
|
+
}
|
|
300
|
+
return target.includes(value)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return compareValues(value, target, operand)
|
|
304
|
+
})
|
|
150
305
|
})
|
|
151
|
-
}
|
|
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
|
+
})
|
|
318
|
+
}
|
|
152
319
|
}
|
|
153
320
|
|
|
154
|
-
if (
|
|
321
|
+
if (sorts) {
|
|
322
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
323
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
324
|
+
filteredData.sort((a, b) => {
|
|
325
|
+
for (const sort of parsedSorts) {
|
|
326
|
+
if (!sort.field) continue
|
|
327
|
+
// Map label to column ID
|
|
328
|
+
const field = labelToIdMap[sort.field] || sort.field
|
|
329
|
+
const aVal = getNestedValue(a, field)
|
|
330
|
+
const bVal = getNestedValue(b, field)
|
|
331
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
332
|
+
|
|
333
|
+
let comparison = 0
|
|
334
|
+
if (aVal === null || aVal === undefined) {
|
|
335
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
336
|
+
} else if (bVal === null || bVal === undefined) {
|
|
337
|
+
comparison = 1
|
|
338
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
339
|
+
comparison = aVal - bVal
|
|
340
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
341
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
342
|
+
} else {
|
|
343
|
+
const aStr = String(aVal)
|
|
344
|
+
const bStr = String(bVal)
|
|
345
|
+
if (aStr < bStr) comparison = -1
|
|
346
|
+
else if (aStr > bStr) comparison = 1
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
350
|
+
}
|
|
351
|
+
return 0
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
} else if (sortBy) {
|
|
155
355
|
filteredData.sort((a, b) => {
|
|
156
|
-
|
|
157
|
-
const
|
|
356
|
+
// Map label to column ID
|
|
357
|
+
const field = labelToIdMap[sortBy] || sortBy
|
|
358
|
+
const aVal = getNestedValue(a, field)
|
|
359
|
+
const bVal = getNestedValue(b, field)
|
|
158
360
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
361
|
+
|
|
362
|
+
let comparison = 0
|
|
363
|
+
if (aVal === null || aVal === undefined) {
|
|
364
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
365
|
+
} else if (bVal === null || bVal === undefined) {
|
|
366
|
+
comparison = 1
|
|
367
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
368
|
+
comparison = aVal - bVal
|
|
369
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
370
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
371
|
+
} else {
|
|
372
|
+
const aStr = String(aVal)
|
|
373
|
+
const bStr = String(bVal)
|
|
374
|
+
if (aStr < bStr) comparison = -1
|
|
375
|
+
else if (aStr > bStr) comparison = 1
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return comparison * sortOrderValue
|
|
162
379
|
})
|
|
163
380
|
}
|
|
164
381
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateDateFormatterCode,
|
|
3
|
+
generateSortFilterHelperCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
6
|
+
|
|
1
7
|
export const validateJavaScriptConfig = (
|
|
2
8
|
config: Record<string, unknown>
|
|
3
9
|
): { isValid: boolean; error?: string } => {
|
|
@@ -35,25 +41,31 @@ interface JavaScriptConfig {
|
|
|
35
41
|
|
|
36
42
|
export const generateJavaScriptFetcher = (config: Record<string, unknown>): string => {
|
|
37
43
|
const jsConfig = config as JavaScriptConfig
|
|
38
|
-
return
|
|
44
|
+
return `${generateSafeJSONParseCode()}
|
|
45
|
+
|
|
46
|
+
${generateDateFormatterCode()}
|
|
47
|
+
|
|
48
|
+
${generateSortFilterHelperCode()}
|
|
49
|
+
|
|
50
|
+
export default async function handler(req, res) {
|
|
39
51
|
try {
|
|
40
|
-
const { limit, offset, page, perPage, query, queryColumns, sortBy, sortOrder, filters } = req.query
|
|
52
|
+
const { limit, offset, page, perPage, query, queryColumns, sortBy, sortOrder, filters, sorts } = req.query
|
|
41
53
|
|
|
42
54
|
const code = ${JSON.stringify(jsConfig.code)}
|
|
43
55
|
const executeCode = new Function('return ' + code)
|
|
44
56
|
let data = executeCode()
|
|
45
57
|
|
|
46
58
|
if (Array.isArray(data)) {
|
|
47
|
-
// 1. Apply search filter
|
|
48
59
|
if (query && query.trim()) {
|
|
49
60
|
const searchQuery = query.toLowerCase()
|
|
50
61
|
|
|
51
62
|
if (queryColumns) {
|
|
52
63
|
try {
|
|
53
|
-
const
|
|
64
|
+
const parsed = safeJSONParse(queryColumns)
|
|
65
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
54
66
|
data = data.filter(item => {
|
|
55
67
|
return columns.some(col => {
|
|
56
|
-
const value = item
|
|
68
|
+
const value = getNestedValue(item, col)
|
|
57
69
|
if (value === null || value === undefined) return false
|
|
58
70
|
return String(value).toLowerCase().includes(searchQuery)
|
|
59
71
|
})
|
|
@@ -73,36 +85,107 @@ export const generateJavaScriptFetcher = (config: Record<string, unknown>): stri
|
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
// 2. Apply custom filters
|
|
77
88
|
if (filters) {
|
|
78
89
|
try {
|
|
79
|
-
const parsedFilters =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
const parsedFilters = safeJSONParse(filters)
|
|
91
|
+
|
|
92
|
+
if (Array.isArray(parsedFilters)) {
|
|
93
|
+
data = data.filter((item) => {
|
|
94
|
+
return parsedFilters.every((filter) => {
|
|
95
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
96
|
+
|
|
97
|
+
const field = filter.source
|
|
98
|
+
const value = getNestedValue(item, field)
|
|
99
|
+
const target = filter.destination
|
|
100
|
+
const operand = filter.operand || '='
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(target)) {
|
|
103
|
+
if (operand === '!=') {
|
|
104
|
+
return !target.includes(value)
|
|
105
|
+
}
|
|
106
|
+
return target.includes(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return compareValues(value, target, operand)
|
|
110
|
+
})
|
|
86
111
|
})
|
|
87
|
-
}
|
|
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
|
+
})
|
|
122
|
+
}
|
|
88
123
|
} catch (err) {
|
|
89
124
|
console.error('Error parsing filters:', err)
|
|
90
125
|
}
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
if (sorts) {
|
|
129
|
+
try {
|
|
130
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
131
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
132
|
+
data.sort((a, b) => {
|
|
133
|
+
for (const sort of parsedSorts) {
|
|
134
|
+
if (!sort.field) continue
|
|
135
|
+
const aVal = getNestedValue(a, sort.field)
|
|
136
|
+
const bVal = getNestedValue(b, sort.field)
|
|
137
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
138
|
+
|
|
139
|
+
let comparison = 0
|
|
140
|
+
if (aVal === null || aVal === undefined) {
|
|
141
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
142
|
+
} else if (bVal === null || bVal === undefined) {
|
|
143
|
+
comparison = 1
|
|
144
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
145
|
+
comparison = aVal - bVal
|
|
146
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
147
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
148
|
+
} else {
|
|
149
|
+
const aStr = String(aVal)
|
|
150
|
+
const bStr = String(bVal)
|
|
151
|
+
if (aStr < bStr) comparison = -1
|
|
152
|
+
else if (aStr > bStr) comparison = 1
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
156
|
+
}
|
|
157
|
+
return 0
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error('Error parsing sorts:', err)
|
|
162
|
+
}
|
|
163
|
+
} else if (sortBy && sortBy.trim()) {
|
|
95
164
|
data.sort((a, b) => {
|
|
96
|
-
const aVal = a
|
|
97
|
-
const bVal = b
|
|
165
|
+
const aVal = getNestedValue(a, sortBy)
|
|
166
|
+
const bVal = getNestedValue(b, sortBy)
|
|
98
167
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
|
|
169
|
+
let comparison = 0
|
|
170
|
+
if (aVal === null || aVal === undefined) {
|
|
171
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
172
|
+
} else if (bVal === null || bVal === undefined) {
|
|
173
|
+
comparison = 1
|
|
174
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
175
|
+
comparison = aVal - bVal
|
|
176
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
177
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
178
|
+
} else {
|
|
179
|
+
const aStr = String(aVal)
|
|
180
|
+
const bStr = String(bVal)
|
|
181
|
+
if (aStr < bStr) comparison = -1
|
|
182
|
+
else if (aStr > bStr) comparison = 1
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return comparison * sortOrderValue
|
|
102
186
|
})
|
|
103
187
|
}
|
|
104
188
|
|
|
105
|
-
// 4. Apply pagination
|
|
106
189
|
const limitValue = limit || perPage
|
|
107
190
|
const pageValue = page ? Math.max(1, parseInt(page)) : undefined
|
|
108
191
|
const offsetValue = offset !== undefined ? Math.max(0, parseInt(offset)) : (pageValue && perPage ? (pageValue - 1) * Math.max(1, parseInt(perPage)) : 0)
|
|
@@ -115,7 +198,7 @@ export const generateJavaScriptFetcher = (config: Record<string, unknown>): stri
|
|
|
115
198
|
}
|
|
116
199
|
}
|
|
117
200
|
|
|
118
|
-
const safeData = JSON.parse(JSON.stringify(data))
|
|
201
|
+
const safeData = JSON.parse(JSON.stringify(data, dateReplacer))
|
|
119
202
|
|
|
120
203
|
return res.status(200).json({
|
|
121
204
|
success: true,
|