@teleporthq/teleport-plugin-next-data-source 0.40.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/ARRAY_MAPPER_PAGINATION.md +1128 -0
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/SEARCH_IMPLEMENTATION_SUMMARY.md +983 -0
- package/__tests__/fetchers.test.ts +545 -0
- package/__tests__/integration.test.ts +561 -0
- package/__tests__/mocks.ts +241 -0
- package/__tests__/pagination.test.ts +31 -0
- package/__tests__/plugin.test.ts +577 -0
- package/__tests__/utils.test.ts +430 -0
- package/__tests__/validation.test.ts +348 -0
- package/dist/cjs/array-mapper-pagination.d.ts +32 -0
- package/dist/cjs/array-mapper-pagination.d.ts.map +1 -0
- package/dist/cjs/array-mapper-pagination.js +77 -0
- package/dist/cjs/array-mapper-pagination.js.map +1 -0
- package/dist/cjs/count-fetchers.d.ts +12 -0
- package/dist/cjs/count-fetchers.d.ts.map +1 -0
- package/dist/cjs/count-fetchers.js +46 -0
- package/dist/cjs/count-fetchers.js.map +1 -0
- package/dist/cjs/data-source-fetchers.d.ts +14 -0
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -0
- package/dist/cjs/data-source-fetchers.js +185 -0
- package/dist/cjs/data-source-fetchers.js.map +1 -0
- package/dist/cjs/fetchers/airtable.d.ts +6 -0
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -0
- package/dist/cjs/fetchers/airtable.js +27 -0
- package/dist/cjs/fetchers/airtable.js.map +1 -0
- package/dist/cjs/fetchers/clickhouse.d.ts +6 -0
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -0
- package/dist/cjs/fetchers/clickhouse.js +29 -0
- package/dist/cjs/fetchers/clickhouse.js.map +1 -0
- package/dist/cjs/fetchers/csv-file.d.ts +7 -0
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -0
- package/dist/cjs/fetchers/csv-file.js +36 -0
- package/dist/cjs/fetchers/csv-file.js.map +1 -0
- package/dist/cjs/fetchers/firestore.d.ts +6 -0
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -0
- package/dist/cjs/fetchers/firestore.js +35 -0
- package/dist/cjs/fetchers/firestore.js.map +1 -0
- package/dist/cjs/fetchers/google-sheets.d.ts +6 -0
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -0
- package/dist/cjs/fetchers/google-sheets.js +30 -0
- package/dist/cjs/fetchers/google-sheets.js.map +1 -0
- package/dist/cjs/fetchers/index.d.ts +17 -0
- package/dist/cjs/fetchers/index.d.ts.map +1 -0
- package/dist/cjs/fetchers/index.js +56 -0
- package/dist/cjs/fetchers/index.js.map +1 -0
- package/dist/cjs/fetchers/javascript.d.ts +7 -0
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -0
- package/dist/cjs/fetchers/javascript.js +40 -0
- package/dist/cjs/fetchers/javascript.js.map +1 -0
- package/dist/cjs/fetchers/mariadb.d.ts +3 -0
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -0
- package/dist/cjs/fetchers/mariadb.js +23 -0
- package/dist/cjs/fetchers/mariadb.js.map +1 -0
- package/dist/cjs/fetchers/mongodb.d.ts +7 -0
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -0
- package/dist/cjs/fetchers/mongodb.js +52 -0
- package/dist/cjs/fetchers/mongodb.js.map +1 -0
- package/dist/cjs/fetchers/mysql.d.ts +3 -0
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -0
- package/dist/cjs/fetchers/mysql.js +30 -0
- package/dist/cjs/fetchers/mysql.js.map +1 -0
- package/dist/cjs/fetchers/postgresql.d.ts +3 -0
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -0
- package/dist/cjs/fetchers/postgresql.js +25 -0
- package/dist/cjs/fetchers/postgresql.js.map +1 -0
- package/dist/cjs/fetchers/redis.d.ts +6 -0
- package/dist/cjs/fetchers/redis.d.ts.map +1 -0
- package/dist/cjs/fetchers/redis.js +46 -0
- package/dist/cjs/fetchers/redis.js.map +1 -0
- package/dist/cjs/fetchers/redshift.d.ts +2 -0
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -0
- package/dist/cjs/fetchers/redshift.js +24 -0
- package/dist/cjs/fetchers/redshift.js.map +1 -0
- package/dist/cjs/fetchers/rest-api.d.ts +6 -0
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -0
- package/dist/cjs/fetchers/rest-api.js +58 -0
- package/dist/cjs/fetchers/rest-api.js.map +1 -0
- package/dist/cjs/fetchers/static-collection.d.ts +7 -0
- package/dist/cjs/fetchers/static-collection.d.ts.map +1 -0
- package/dist/cjs/fetchers/static-collection.js +24 -0
- package/dist/cjs/fetchers/static-collection.js.map +1 -0
- package/dist/cjs/fetchers/supabase.d.ts +7 -0
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -0
- package/dist/cjs/fetchers/supabase.js +42 -0
- package/dist/cjs/fetchers/supabase.js.map +1 -0
- package/dist/cjs/fetchers/turso.d.ts +6 -0
- package/dist/cjs/fetchers/turso.d.ts.map +1 -0
- package/dist/cjs/fetchers/turso.js +25 -0
- package/dist/cjs/fetchers/turso.js.map +1 -0
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +325 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts +5 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -0
- package/dist/cjs/pagination-plugin.js +1484 -0
- package/dist/cjs/pagination-plugin.js.map +1 -0
- package/dist/cjs/pagination-with-count.d.ts +6 -0
- package/dist/cjs/pagination-with-count.d.ts.map +1 -0
- package/dist/cjs/pagination-with-count.js +63 -0
- package/dist/cjs/pagination-with-count.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/cjs/utils.d.ts +31 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +763 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/cjs/validation.d.ts +5 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +29 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/array-mapper-pagination.d.ts +32 -0
- package/dist/esm/array-mapper-pagination.d.ts.map +1 -0
- package/dist/esm/array-mapper-pagination.js +72 -0
- package/dist/esm/array-mapper-pagination.js.map +1 -0
- package/dist/esm/count-fetchers.d.ts +12 -0
- package/dist/esm/count-fetchers.d.ts.map +1 -0
- package/dist/esm/count-fetchers.js +35 -0
- package/dist/esm/count-fetchers.js.map +1 -0
- package/dist/esm/data-source-fetchers.d.ts +14 -0
- package/dist/esm/data-source-fetchers.d.ts.map +1 -0
- package/dist/esm/data-source-fetchers.js +179 -0
- package/dist/esm/data-source-fetchers.js.map +1 -0
- package/dist/esm/fetchers/airtable.d.ts +6 -0
- package/dist/esm/fetchers/airtable.d.ts.map +1 -0
- package/dist/esm/fetchers/airtable.js +22 -0
- package/dist/esm/fetchers/airtable.js.map +1 -0
- package/dist/esm/fetchers/clickhouse.d.ts +6 -0
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -0
- package/dist/esm/fetchers/clickhouse.js +24 -0
- package/dist/esm/fetchers/clickhouse.js.map +1 -0
- package/dist/esm/fetchers/csv-file.d.ts +7 -0
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -0
- package/dist/esm/fetchers/csv-file.js +30 -0
- package/dist/esm/fetchers/csv-file.js.map +1 -0
- package/dist/esm/fetchers/firestore.d.ts +6 -0
- package/dist/esm/fetchers/firestore.d.ts.map +1 -0
- package/dist/esm/fetchers/firestore.js +30 -0
- package/dist/esm/fetchers/firestore.js.map +1 -0
- package/dist/esm/fetchers/google-sheets.d.ts +6 -0
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -0
- package/dist/esm/fetchers/google-sheets.js +25 -0
- package/dist/esm/fetchers/google-sheets.js.map +1 -0
- package/dist/esm/fetchers/index.d.ts +17 -0
- package/dist/esm/fetchers/index.d.ts.map +1 -0
- package/dist/esm/fetchers/index.js +17 -0
- package/dist/esm/fetchers/index.js.map +1 -0
- package/dist/esm/fetchers/javascript.d.ts +7 -0
- package/dist/esm/fetchers/javascript.d.ts.map +1 -0
- package/dist/esm/fetchers/javascript.js +34 -0
- package/dist/esm/fetchers/javascript.js.map +1 -0
- package/dist/esm/fetchers/mariadb.d.ts +3 -0
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -0
- package/dist/esm/fetchers/mariadb.js +18 -0
- package/dist/esm/fetchers/mariadb.js.map +1 -0
- package/dist/esm/fetchers/mongodb.d.ts +7 -0
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -0
- package/dist/esm/fetchers/mongodb.js +46 -0
- package/dist/esm/fetchers/mongodb.js.map +1 -0
- package/dist/esm/fetchers/mysql.d.ts +3 -0
- package/dist/esm/fetchers/mysql.d.ts.map +1 -0
- package/dist/esm/fetchers/mysql.js +25 -0
- package/dist/esm/fetchers/mysql.js.map +1 -0
- package/dist/esm/fetchers/postgresql.d.ts +3 -0
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -0
- package/dist/esm/fetchers/postgresql.js +20 -0
- package/dist/esm/fetchers/postgresql.js.map +1 -0
- package/dist/esm/fetchers/redis.d.ts +6 -0
- package/dist/esm/fetchers/redis.d.ts.map +1 -0
- package/dist/esm/fetchers/redis.js +41 -0
- package/dist/esm/fetchers/redis.js.map +1 -0
- package/dist/esm/fetchers/redshift.d.ts +2 -0
- package/dist/esm/fetchers/redshift.d.ts.map +1 -0
- package/dist/esm/fetchers/redshift.js +20 -0
- package/dist/esm/fetchers/redshift.js.map +1 -0
- package/dist/esm/fetchers/rest-api.d.ts +6 -0
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -0
- package/dist/esm/fetchers/rest-api.js +53 -0
- package/dist/esm/fetchers/rest-api.js.map +1 -0
- package/dist/esm/fetchers/static-collection.d.ts +7 -0
- package/dist/esm/fetchers/static-collection.d.ts.map +1 -0
- package/dist/esm/fetchers/static-collection.js +18 -0
- package/dist/esm/fetchers/static-collection.js.map +1 -0
- package/dist/esm/fetchers/supabase.d.ts +7 -0
- package/dist/esm/fetchers/supabase.d.ts.map +1 -0
- package/dist/esm/fetchers/supabase.js +36 -0
- package/dist/esm/fetchers/supabase.js.map +1 -0
- package/dist/esm/fetchers/turso.d.ts +6 -0
- package/dist/esm/fetchers/turso.d.ts.map +1 -0
- package/dist/esm/fetchers/turso.js +20 -0
- package/dist/esm/fetchers/turso.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +306 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts +5 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -0
- package/dist/esm/pagination-plugin.js +1457 -0
- package/dist/esm/pagination-plugin.js.map +1 -0
- package/dist/esm/pagination-with-count.d.ts +6 -0
- package/dist/esm/pagination-with-count.d.ts.map +1 -0
- package/dist/esm/pagination-with-count.js +34 -0
- package/dist/esm/pagination-with-count.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/utils.d.ts +31 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +722 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validation.d.ts +5 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +25 -0
- package/dist/esm/validation.js.map +1 -0
- package/package.json +33 -0
- package/src/array-mapper-pagination.ts +113 -0
- package/src/count-fetchers.ts +99 -0
- package/src/data-source-fetchers.ts +313 -0
- package/src/fetchers/airtable.ts +153 -0
- package/src/fetchers/clickhouse.ts +127 -0
- package/src/fetchers/csv-file.ts +163 -0
- package/src/fetchers/firestore.ts +138 -0
- package/src/fetchers/google-sheets.ts +189 -0
- package/src/fetchers/index.ts +32 -0
- package/src/fetchers/javascript.ts +150 -0
- package/src/fetchers/mariadb.ts +230 -0
- package/src/fetchers/mongodb.ts +239 -0
- package/src/fetchers/mysql.ts +237 -0
- package/src/fetchers/postgresql.ts +247 -0
- package/src/fetchers/redis.ts +152 -0
- package/src/fetchers/redshift.ts +138 -0
- package/src/fetchers/rest-api.ts +148 -0
- package/src/fetchers/static-collection.ts +149 -0
- package/src/fetchers/supabase.ts +246 -0
- package/src/fetchers/turso.ts +131 -0
- package/src/index.ts +352 -0
- package/src/pagination-plugin.ts +2335 -0
- package/src/pagination-with-count.ts +89 -0
- package/src/utils.ts +1013 -0
- package/src/validation.ts +32 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export const validateGoogleSheetsConfig = (
|
|
2
|
+
config: Record<string, unknown>
|
|
3
|
+
): { isValid: boolean; error?: string } => {
|
|
4
|
+
if (!config || typeof config !== 'object') {
|
|
5
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!config.sheetId && !config.sheetUrl) {
|
|
9
|
+
return { isValid: false, error: 'Google Sheets ID or URL is required' }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (config.sheetId && typeof config.sheetId !== 'string') {
|
|
13
|
+
return { isValid: false, error: 'Sheet ID must be a string' }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (config.sheetUrl) {
|
|
17
|
+
if (typeof config.sheetUrl !== 'string') {
|
|
18
|
+
return { isValid: false, error: 'Sheet URL must be a string' }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!config.sheetUrl.includes('docs.google.com/spreadsheets')) {
|
|
22
|
+
return { isValid: false, error: 'Invalid Google Sheets URL format' }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { isValid: true }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface GoogleSheetsConfig {
|
|
30
|
+
sheetId?: string
|
|
31
|
+
sheetUrl?: string
|
|
32
|
+
apiKey?: string
|
|
33
|
+
sheetName?: string
|
|
34
|
+
range?: string
|
|
35
|
+
maxRows?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const generateGoogleSheetsFetcher = (config: Record<string, unknown>): string => {
|
|
39
|
+
const sheetsConfig = config as GoogleSheetsConfig
|
|
40
|
+
return `import fetch from 'node-fetch'
|
|
41
|
+
|
|
42
|
+
export default async function handler(req, res) {
|
|
43
|
+
try {
|
|
44
|
+
const sheetUrl = ${JSON.stringify(sheetsConfig.sheetUrl)}
|
|
45
|
+
let sheetId = ${JSON.stringify(sheetsConfig.sheetId)}
|
|
46
|
+
const range = ${JSON.stringify(sheetsConfig.range || 'A1:Z1000')}
|
|
47
|
+
const maxRows = ${sheetsConfig.maxRows || 0}
|
|
48
|
+
|
|
49
|
+
if (!sheetId && sheetUrl) {
|
|
50
|
+
const match = sheetUrl.match(/\\/d\\/([a-zA-Z0-9-_]+)/)
|
|
51
|
+
sheetId = match ? match[1] : undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!sheetId) {
|
|
55
|
+
return res.status(400).json({
|
|
56
|
+
success: false,
|
|
57
|
+
error: 'Invalid Google Sheets URL or Sheet ID',
|
|
58
|
+
timestamp: Date.now()
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let url = \`https://docs.google.com/spreadsheets/d/\${sheetId}/gviz/tq?tqx=out:json&range=\${range}\`
|
|
63
|
+
|
|
64
|
+
if (maxRows && maxRows > 0) {
|
|
65
|
+
url += \`&tq=limit \${maxRows}\`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const response = await fetch(url)
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
return res.status(response.status).json({
|
|
72
|
+
success: false,
|
|
73
|
+
error: \`HTTP \${response.status}: \${response.statusText}\`,
|
|
74
|
+
timestamp: Date.now()
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const text = await response.text()
|
|
79
|
+
const jsonMatch = text.match(/google\\.visualization\\.Query\\.setResponse\\((.*)\\);/)
|
|
80
|
+
|
|
81
|
+
if (!jsonMatch) {
|
|
82
|
+
return res.status(500).json({
|
|
83
|
+
success: false,
|
|
84
|
+
error: 'Unable to parse Google Sheets response',
|
|
85
|
+
timestamp: Date.now()
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const data = JSON.parse(jsonMatch[1])
|
|
90
|
+
|
|
91
|
+
if (data.status === 'error') {
|
|
92
|
+
return res.status(500).json({
|
|
93
|
+
success: false,
|
|
94
|
+
error: data.errors?.[0]?.detailed_message || 'Failed to fetch Google Sheets data',
|
|
95
|
+
timestamp: Date.now()
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const table = data.table
|
|
100
|
+
const columns = table.cols.map((col, index) => ({
|
|
101
|
+
id: col.id || \`col_\${index}\`,
|
|
102
|
+
label: col.label || \`Column \${index + 1}\`,
|
|
103
|
+
type: col.type || 'string'
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
const rows = table.rows.map((row) => {
|
|
107
|
+
const rowData = {}
|
|
108
|
+
row.c.forEach((cell, index) => {
|
|
109
|
+
const columnId = columns[index].id
|
|
110
|
+
rowData[columnId] = cell?.v ?? null
|
|
111
|
+
})
|
|
112
|
+
return rowData
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
|
|
116
|
+
|
|
117
|
+
let filteredData = [...rows]
|
|
118
|
+
|
|
119
|
+
if (query) {
|
|
120
|
+
const searchQuery = query.toLowerCase()
|
|
121
|
+
|
|
122
|
+
if (queryColumns) {
|
|
123
|
+
const searchColumns = JSON.parse(queryColumns)
|
|
124
|
+
filteredData = filteredData.filter((item) => {
|
|
125
|
+
return searchColumns.some((col) => {
|
|
126
|
+
const value = item[col]
|
|
127
|
+
return value && String(value).toLowerCase().includes(searchQuery)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
} else {
|
|
131
|
+
filteredData = filteredData.filter((item) => {
|
|
132
|
+
try {
|
|
133
|
+
const stringified = JSON.stringify(item).toLowerCase()
|
|
134
|
+
return stringified.includes(searchQuery)
|
|
135
|
+
} catch {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (filters) {
|
|
143
|
+
const parsedFilters = JSON.parse(filters)
|
|
144
|
+
filteredData = filteredData.filter((item) => {
|
|
145
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
146
|
+
if (Array.isArray(value)) {
|
|
147
|
+
return value.includes(item[key])
|
|
148
|
+
}
|
|
149
|
+
return item[key] === value
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (sortBy) {
|
|
155
|
+
filteredData.sort((a, b) => {
|
|
156
|
+
const aVal = a[sortBy]
|
|
157
|
+
const bVal = b[sortBy]
|
|
158
|
+
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
159
|
+
if (aVal < bVal) return -sortOrderValue
|
|
160
|
+
if (aVal > bVal) return sortOrderValue
|
|
161
|
+
return 0
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const limitValue = limit || perPage
|
|
166
|
+
const offsetValue = offsetParam !== undefined ? parseInt(offsetParam) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
|
|
167
|
+
|
|
168
|
+
if (limitValue) {
|
|
169
|
+
filteredData = filteredData.slice(offsetValue, offsetValue + parseInt(limitValue))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const safeData = JSON.parse(JSON.stringify(filteredData))
|
|
173
|
+
|
|
174
|
+
return res.status(200).json({
|
|
175
|
+
success: true,
|
|
176
|
+
data: safeData,
|
|
177
|
+
timestamp: Date.now()
|
|
178
|
+
})
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('Google Sheets fetch error:', error)
|
|
181
|
+
return res.status(500).json({
|
|
182
|
+
success: false,
|
|
183
|
+
error: error.message || 'Failed to fetch data',
|
|
184
|
+
timestamp: Date.now()
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
`
|
|
189
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { generatePostgreSQLFetcher, generatePostgreSQLCountFetcher } from './postgresql'
|
|
2
|
+
export { generateMySQLFetcher, generateMySQLCountFetcher } from './mysql'
|
|
3
|
+
export { generateMariaDBFetcher, generateMariaDBCountFetcher } from './mariadb'
|
|
4
|
+
export { generateRedshiftFetcher } from './redshift'
|
|
5
|
+
export {
|
|
6
|
+
generateMongoDBFetcher,
|
|
7
|
+
generateMongoDBCountFetcher,
|
|
8
|
+
validateMongoDBConfig,
|
|
9
|
+
} from './mongodb'
|
|
10
|
+
export { generateRedisFetcher, validateRedisConfig } from './redis'
|
|
11
|
+
export { generateFirestoreFetcher, validateFirestoreConfig } from './firestore'
|
|
12
|
+
export { generateClickHouseFetcher, validateClickHouseConfig } from './clickhouse'
|
|
13
|
+
export { generateAirtableFetcher, validateAirtableConfig } from './airtable'
|
|
14
|
+
export {
|
|
15
|
+
generateSupabaseFetcher,
|
|
16
|
+
generateSupabaseCountFetcher,
|
|
17
|
+
validateSupabaseConfig,
|
|
18
|
+
} from './supabase'
|
|
19
|
+
export { generateTursoFetcher, validateTursoConfig } from './turso'
|
|
20
|
+
export { generateRESTAPIFetcher, validateRESTAPIConfig } from './rest-api'
|
|
21
|
+
export {
|
|
22
|
+
generateJavaScriptFetcher,
|
|
23
|
+
generateJavaScriptCountFetcher,
|
|
24
|
+
validateJavaScriptConfig,
|
|
25
|
+
} from './javascript'
|
|
26
|
+
export { generateCSVFileFetcher, generateCSVCountFetcher, validateCSVConfig } from './csv-file'
|
|
27
|
+
export {
|
|
28
|
+
generateStaticCollectionFetcher,
|
|
29
|
+
generateStaticCollectionCountFetcher,
|
|
30
|
+
validateStaticCollectionConfig,
|
|
31
|
+
} from './static-collection'
|
|
32
|
+
export { generateGoogleSheetsFetcher, validateGoogleSheetsConfig } from './google-sheets'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const validateJavaScriptConfig = (
|
|
2
|
+
config: Record<string, unknown>
|
|
3
|
+
): { isValid: boolean; error?: string } => {
|
|
4
|
+
if (!config || typeof config !== 'object') {
|
|
5
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!config.code || typeof config.code !== 'string' || config.code.trim() === '') {
|
|
9
|
+
return { isValid: false, error: 'JavaScript code is required' }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const dangerousPatterns = [
|
|
13
|
+
/require\s*\(/i,
|
|
14
|
+
/import\s+/i,
|
|
15
|
+
/eval\s*\(/i,
|
|
16
|
+
/Function\s*\(/i,
|
|
17
|
+
/process\./i,
|
|
18
|
+
/global\./i,
|
|
19
|
+
/\.exec\s*\(/i,
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
for (const pattern of dangerousPatterns) {
|
|
23
|
+
if (pattern.test(config.code)) {
|
|
24
|
+
console.warn('[Data Source] Warning: JavaScript code contains potentially dangerous patterns')
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { isValid: true }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface JavaScriptConfig {
|
|
33
|
+
code?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const generateJavaScriptFetcher = (config: Record<string, unknown>): string => {
|
|
37
|
+
const jsConfig = config as JavaScriptConfig
|
|
38
|
+
return `export default async function handler(req, res) {
|
|
39
|
+
try {
|
|
40
|
+
const { limit, offset, page, perPage, query, queryColumns } = req.query
|
|
41
|
+
|
|
42
|
+
const code = ${JSON.stringify(jsConfig.code)}
|
|
43
|
+
const executeCode = new Function('return ' + code)
|
|
44
|
+
let data = executeCode()
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(data)) {
|
|
47
|
+
if (query) {
|
|
48
|
+
const searchQuery = query.toLowerCase()
|
|
49
|
+
|
|
50
|
+
if (queryColumns) {
|
|
51
|
+
// Search specific columns
|
|
52
|
+
const columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
|
|
53
|
+
data = data.filter(item => {
|
|
54
|
+
return columns.some(col => {
|
|
55
|
+
const value = item[col]
|
|
56
|
+
if (value === null || value === undefined) return false
|
|
57
|
+
return String(value).toLowerCase().includes(searchQuery)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
} else {
|
|
61
|
+
// Search across all fields by stringifying the entire record
|
|
62
|
+
data = data.filter(item => {
|
|
63
|
+
try {
|
|
64
|
+
const stringified = JSON.stringify(item).toLowerCase()
|
|
65
|
+
return stringified.includes(searchQuery)
|
|
66
|
+
} catch {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const limitValue = limit || perPage
|
|
74
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
|
|
75
|
+
|
|
76
|
+
if (limitValue) {
|
|
77
|
+
data = data.slice(offsetValue, offsetValue + parseInt(limitValue))
|
|
78
|
+
} else if (offsetValue > 0) {
|
|
79
|
+
data = data.slice(offsetValue)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const safeData = JSON.parse(JSON.stringify(data))
|
|
84
|
+
|
|
85
|
+
return res.status(200).json({
|
|
86
|
+
success: true,
|
|
87
|
+
data: safeData,
|
|
88
|
+
timestamp: Date.now()
|
|
89
|
+
})
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('JavaScript execution error:', error)
|
|
92
|
+
return res.status(500).json({
|
|
93
|
+
success: false,
|
|
94
|
+
error: error.message || 'Failed to execute code',
|
|
95
|
+
timestamp: Date.now()
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// tslint:disable-next-line:variable-name
|
|
103
|
+
export const generateJavaScriptCountFetcher = (_config: any): string => {
|
|
104
|
+
return `
|
|
105
|
+
async function getCount(req, res) {
|
|
106
|
+
try {
|
|
107
|
+
const { query, queryColumns } = req.query
|
|
108
|
+
const fakeReq = { query: { query, queryColumns }, method: 'GET' }
|
|
109
|
+
let result = null
|
|
110
|
+
let statusCode = 200
|
|
111
|
+
|
|
112
|
+
const fakeRes = {
|
|
113
|
+
status: (code) => {
|
|
114
|
+
statusCode = code
|
|
115
|
+
return fakeRes
|
|
116
|
+
},
|
|
117
|
+
json: (data) => {
|
|
118
|
+
result = data
|
|
119
|
+
return fakeRes
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await handler(fakeReq, fakeRes)
|
|
124
|
+
|
|
125
|
+
if (statusCode !== 200 || !result || !result.success) {
|
|
126
|
+
return res.status(500).json({
|
|
127
|
+
success: false,
|
|
128
|
+
error: 'Failed to get data for counting',
|
|
129
|
+
timestamp: Date.now()
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const count = Array.isArray(result.data) ? result.data.length : 0
|
|
134
|
+
|
|
135
|
+
return res.status(200).json({
|
|
136
|
+
success: true,
|
|
137
|
+
count: count,
|
|
138
|
+
timestamp: Date.now()
|
|
139
|
+
})
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Error getting count:', error)
|
|
142
|
+
return res.status(500).json({
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message || 'Failed to get count',
|
|
145
|
+
timestamp: Date.now()
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`
|
|
150
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
interface MariaDBConfig {
|
|
4
|
+
host?: string
|
|
5
|
+
port?: number
|
|
6
|
+
user?: string
|
|
7
|
+
username?: string
|
|
8
|
+
password?: string
|
|
9
|
+
database?: string
|
|
10
|
+
ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
11
|
+
sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const generateMariaDBFetcher = (
|
|
15
|
+
config: Record<string, unknown>,
|
|
16
|
+
tableName: string
|
|
17
|
+
): string => {
|
|
18
|
+
const mariaConfig = config as MariaDBConfig
|
|
19
|
+
const database = mariaConfig.database
|
|
20
|
+
|
|
21
|
+
return `import mariadb from 'mariadb'
|
|
22
|
+
|
|
23
|
+
export default async function handler(req, res) {
|
|
24
|
+
let pool = null
|
|
25
|
+
try {
|
|
26
|
+
pool = mariadb.createPool({
|
|
27
|
+
host: ${JSON.stringify(mariaConfig.host)},
|
|
28
|
+
port: ${mariaConfig.port || 3306},
|
|
29
|
+
user: ${JSON.stringify(mariaConfig.user)},
|
|
30
|
+
password: ${replaceSecretReference(mariaConfig.password)},
|
|
31
|
+
database: ${JSON.stringify(mariaConfig.database)},
|
|
32
|
+
ssl: ${mariaConfig.ssl || false}${
|
|
33
|
+
mariaConfig.sslConfig
|
|
34
|
+
? `,
|
|
35
|
+
sslConfig: {
|
|
36
|
+
${
|
|
37
|
+
mariaConfig.sslConfig.ca ? `ca: ${replaceSecretReference(mariaConfig.sslConfig.ca)},` : ''
|
|
38
|
+
}
|
|
39
|
+
${
|
|
40
|
+
mariaConfig.sslConfig.cert
|
|
41
|
+
? `cert: ${replaceSecretReference(mariaConfig.sslConfig.cert)},`
|
|
42
|
+
: ''
|
|
43
|
+
}
|
|
44
|
+
${
|
|
45
|
+
mariaConfig.sslConfig.key
|
|
46
|
+
? `key: ${replaceSecretReference(mariaConfig.sslConfig.key)},`
|
|
47
|
+
: ''
|
|
48
|
+
}
|
|
49
|
+
rejectUnauthorized: ${mariaConfig.sslConfig.rejectUnauthorized !== false}
|
|
50
|
+
}`
|
|
51
|
+
: ''
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const connection = await pool.getConnection()
|
|
56
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
57
|
+
|
|
58
|
+
const conditions = []
|
|
59
|
+
const queryParams = []
|
|
60
|
+
|
|
61
|
+
if (query) {
|
|
62
|
+
let columns = []
|
|
63
|
+
|
|
64
|
+
if (queryColumns) {
|
|
65
|
+
// Use specified columns
|
|
66
|
+
columns = JSON.parse(queryColumns)
|
|
67
|
+
} else {
|
|
68
|
+
// Fallback: Get all columns from information_schema
|
|
69
|
+
try {
|
|
70
|
+
const schemaRows = await connection.query(
|
|
71
|
+
\`SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
|
72
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
73
|
+
ORDER BY ORDINAL_POSITION\`,
|
|
74
|
+
[${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
|
|
75
|
+
)
|
|
76
|
+
columns = schemaRows.map(row => row.COLUMN_NAME)
|
|
77
|
+
} catch (schemaError) {
|
|
78
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
79
|
+
// Continue without search if we can't get columns
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (columns.length > 0) {
|
|
84
|
+
const searchConditions = columns.map((col) => \`CAST(\\\`\${col}\\\` AS CHAR) LIKE ?\`)
|
|
85
|
+
columns.forEach(() => queryParams.push(\`%\${query}%\`))
|
|
86
|
+
conditions.push(\`(\${searchConditions.join(' OR ')})\`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (filters) {
|
|
91
|
+
const parsedFilters = JSON.parse(filters)
|
|
92
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
const placeholders = value.map(() => '?').join(', ')
|
|
95
|
+
queryParams.push(...value)
|
|
96
|
+
conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
|
|
97
|
+
} else {
|
|
98
|
+
conditions.push(\`\\\`\${key}\\\` = ?\`)
|
|
99
|
+
queryParams.push(value)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let sql = \`SELECT * FROM \\\`${tableName}\\\`\`
|
|
105
|
+
|
|
106
|
+
if (conditions.length > 0) {
|
|
107
|
+
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (sortBy) {
|
|
111
|
+
sql += \` ORDER BY \\\`\${sortBy}\\\` \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const limitValue = limit || perPage
|
|
115
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
116
|
+
|
|
117
|
+
if (limitValue) {
|
|
118
|
+
sql += \` LIMIT \${limitValue}\`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (offsetValue !== undefined) {
|
|
122
|
+
sql += \` OFFSET \${offsetValue}\`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rows = await connection.query(sql, queryParams)
|
|
126
|
+
const rowArray = Array.isArray(rows) ? rows : []
|
|
127
|
+
const plainRows = rowArray.map((row) =>
|
|
128
|
+
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
129
|
+
)
|
|
130
|
+
const safeData = JSON.parse(JSON.stringify(plainRows))
|
|
131
|
+
connection.release()
|
|
132
|
+
|
|
133
|
+
return res.status(200).json({
|
|
134
|
+
success: true,
|
|
135
|
+
data: safeData,
|
|
136
|
+
timestamp: Date.now()
|
|
137
|
+
})
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('MariaDB fetch error:', error)
|
|
140
|
+
return res.status(500).json({
|
|
141
|
+
success: false,
|
|
142
|
+
error: error.message || 'Failed to fetch data',
|
|
143
|
+
timestamp: Date.now()
|
|
144
|
+
})
|
|
145
|
+
} finally {
|
|
146
|
+
if (pool) {
|
|
147
|
+
await pool.end()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const generateMariaDBCountFetcher = (
|
|
155
|
+
config: Record<string, unknown>,
|
|
156
|
+
tableName: string
|
|
157
|
+
): string => {
|
|
158
|
+
const mariaConfig = config as MariaDBConfig
|
|
159
|
+
const database = mariaConfig.database
|
|
160
|
+
|
|
161
|
+
return `
|
|
162
|
+
async function getCount(req, res) {
|
|
163
|
+
const connection = getConnection()
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const { query, queryColumns, filters } = req.query
|
|
167
|
+
const conditions = []
|
|
168
|
+
const queryParams = []
|
|
169
|
+
|
|
170
|
+
if (query) {
|
|
171
|
+
let columns = []
|
|
172
|
+
|
|
173
|
+
if (queryColumns) {
|
|
174
|
+
// Use specified columns
|
|
175
|
+
columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
|
|
176
|
+
} else {
|
|
177
|
+
// Fallback: Get all columns from information_schema
|
|
178
|
+
try {
|
|
179
|
+
const schemaRows = await connection.query(
|
|
180
|
+
\`SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
|
181
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
182
|
+
ORDER BY ORDINAL_POSITION\`,
|
|
183
|
+
[${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
|
|
184
|
+
)
|
|
185
|
+
columns = schemaRows.map(row => row.COLUMN_NAME)
|
|
186
|
+
} catch (schemaError) {
|
|
187
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
188
|
+
// Continue without search if we can't get columns
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (columns.length > 0) {
|
|
193
|
+
const searchConditions = columns.map(col => \`CAST(\${col} AS CHAR) LIKE ?\`).join(' OR ')
|
|
194
|
+
conditions.push(\`(\${searchConditions})\`)
|
|
195
|
+
columns.forEach(() => queryParams.push(\`%\${query}%\`))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (filters) {
|
|
200
|
+
const parsedFilters = JSON.parse(filters)
|
|
201
|
+
for (const filter of parsedFilters) {
|
|
202
|
+
conditions.push(\`\${filter.column} \${filter.operator} ?\`)
|
|
203
|
+
queryParams.push(filter.value)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
|
|
208
|
+
if (conditions.length > 0) {
|
|
209
|
+
countSql += \` WHERE \${conditions.join(' AND ')}\`
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const [rows] = await connection.execute(countSql, queryParams)
|
|
213
|
+
const count = rows[0].count
|
|
214
|
+
|
|
215
|
+
return res.status(200).json({
|
|
216
|
+
success: true,
|
|
217
|
+
count: count,
|
|
218
|
+
timestamp: Date.now()
|
|
219
|
+
})
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error('Error getting count:', error)
|
|
222
|
+
return res.status(500).json({
|
|
223
|
+
success: false,
|
|
224
|
+
error: error.message || 'Failed to get count',
|
|
225
|
+
timestamp: Date.now()
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
`
|
|
230
|
+
}
|