@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,153 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
export const validateAirtableConfig = (
|
|
4
|
+
config: Record<string, unknown>
|
|
5
|
+
): { isValid: boolean; error?: string } => {
|
|
6
|
+
if (!config || typeof config !== 'object') {
|
|
7
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!config.baseId || typeof config.baseId !== 'string' || config.baseId.trim() === '') {
|
|
11
|
+
return { isValid: false, error: 'Airtable base ID is required' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!config.personalAccessToken || typeof config.personalAccessToken !== 'string') {
|
|
15
|
+
return { isValid: false, error: 'Airtable personal access token is required' }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { isValid: true }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface AirtableConfig {
|
|
22
|
+
baseId?: string
|
|
23
|
+
personalAccessToken?: string
|
|
24
|
+
selectedTables?: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const generateAirtableFetcher = (
|
|
28
|
+
config: Record<string, unknown>,
|
|
29
|
+
tableName: string
|
|
30
|
+
): string => {
|
|
31
|
+
const airtableConfig = config as AirtableConfig
|
|
32
|
+
const baseId = airtableConfig.baseId
|
|
33
|
+
const personalAccessToken = airtableConfig.personalAccessToken
|
|
34
|
+
|
|
35
|
+
return `import fetch from 'node-fetch'
|
|
36
|
+
|
|
37
|
+
export default async function handler(req, res) {
|
|
38
|
+
try {
|
|
39
|
+
const { query, view, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
|
|
40
|
+
|
|
41
|
+
const queryParams = new URLSearchParams()
|
|
42
|
+
|
|
43
|
+
if (view) {
|
|
44
|
+
queryParams.append('view', view)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (sortBy) {
|
|
48
|
+
queryParams.append('sort[0][field]', sortBy)
|
|
49
|
+
queryParams.append('sort[0][direction]', sortOrder || 'asc')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const perPageValue = limit || perPage || 100
|
|
53
|
+
queryParams.append('pageSize', Math.min(parseInt(perPageValue), 100).toString())
|
|
54
|
+
|
|
55
|
+
if (filters) {
|
|
56
|
+
const parsedFilters = JSON.parse(filters)
|
|
57
|
+
const conditions = Object.entries(parsedFilters).map(([field, value]) => {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
const arrayConditions = value.map((v) => {
|
|
60
|
+
if (typeof v === 'string') {
|
|
61
|
+
return \`{\${field}}='\${v.replace(/'/g, "\\\\'")}'\`
|
|
62
|
+
} else if (typeof v === 'number') {
|
|
63
|
+
return \`{\${field}}=\${v}\`
|
|
64
|
+
} else if (typeof v === 'boolean') {
|
|
65
|
+
return \`{\${field}}=\${v ? 'TRUE()' : 'FALSE()'}\`
|
|
66
|
+
}
|
|
67
|
+
return \`{\${field}}='\${String(v)}'\`
|
|
68
|
+
})
|
|
69
|
+
return arrayConditions.length > 1
|
|
70
|
+
? \`OR(\${arrayConditions.join(',')})\`
|
|
71
|
+
: arrayConditions[0]
|
|
72
|
+
} else if (typeof value === 'string') {
|
|
73
|
+
return \`{\${field}}='\${value.replace(/'/g, "\\\\'")}'\`
|
|
74
|
+
} else if (typeof value === 'number') {
|
|
75
|
+
return \`{\${field}}=\${value}\`
|
|
76
|
+
} else if (typeof value === 'boolean') {
|
|
77
|
+
return \`{\${field}}=\${value ? 'TRUE()' : 'FALSE()'}\`
|
|
78
|
+
}
|
|
79
|
+
return \`{\${field}}='\${String(value)}'\`
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const filterFormula = conditions.length > 1 ? \`AND(\${conditions.join(',')})\` : conditions[0]
|
|
83
|
+
if (filterFormula) {
|
|
84
|
+
queryParams.append('filterByFormula', filterFormula)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let url = \`https://api.airtable.com/v0/${baseId}/\${encodeURIComponent('${tableName}')}\`
|
|
89
|
+
if (queryParams.toString()) {
|
|
90
|
+
url += \`?\${queryParams.toString()}\`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const allRecords = []
|
|
94
|
+
let airtableOffset
|
|
95
|
+
const skipValue = offsetParam !== undefined ? parseInt(offsetParam) : (page ? (parseInt(page) - 1) * parseInt(perPageValue) : 0)
|
|
96
|
+
const totalRecordsNeeded = skipValue + parseInt(perPageValue)
|
|
97
|
+
|
|
98
|
+
do {
|
|
99
|
+
const fetchUrl = airtableOffset ? \`\${url}&offset=\${airtableOffset}\` : url
|
|
100
|
+
const response = await fetch(fetchUrl, {
|
|
101
|
+
method: 'GET',
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: \`Bearer ${replaceSecretReference(personalAccessToken, {
|
|
104
|
+
templateLiteral: true,
|
|
105
|
+
})}\`,
|
|
106
|
+
'Content-Type': 'application/json'
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
const errorData = await response.json().catch(() => ({}))
|
|
112
|
+
return res.status(response.status).json({
|
|
113
|
+
success: false,
|
|
114
|
+
error: errorData.error?.message || \`HTTP \${response.status}: \${response.statusText}\`,
|
|
115
|
+
timestamp: Date.now()
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const data = await response.json()
|
|
120
|
+
allRecords.push(...data.records)
|
|
121
|
+
airtableOffset = data.offset
|
|
122
|
+
|
|
123
|
+
if (allRecords.length >= totalRecordsNeeded || !airtableOffset) {
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
} while (airtableOffset)
|
|
127
|
+
|
|
128
|
+
const paginatedRecords = allRecords.slice(skipValue, skipValue + parseInt(perPageValue))
|
|
129
|
+
|
|
130
|
+
const formattedRecords = paginatedRecords.map((record) => ({
|
|
131
|
+
id: record.id,
|
|
132
|
+
...record.fields,
|
|
133
|
+
createdTime: record.createdTime
|
|
134
|
+
}))
|
|
135
|
+
|
|
136
|
+
const safeData = JSON.parse(JSON.stringify(formattedRecords))
|
|
137
|
+
|
|
138
|
+
return res.status(200).json({
|
|
139
|
+
success: true,
|
|
140
|
+
data: safeData,
|
|
141
|
+
timestamp: Date.now()
|
|
142
|
+
})
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Airtable fetch error:', error)
|
|
145
|
+
return res.status(500).json({
|
|
146
|
+
success: false,
|
|
147
|
+
error: error.message || 'Failed to fetch data',
|
|
148
|
+
timestamp: Date.now()
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`
|
|
153
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
export const validateClickHouseConfig = (
|
|
4
|
+
config: Record<string, unknown>
|
|
5
|
+
): { isValid: boolean; error?: string } => {
|
|
6
|
+
if (!config || typeof config !== 'object') {
|
|
7
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!config.url || typeof config.url !== 'string') {
|
|
11
|
+
return { isValid: false, error: 'ClickHouse URL is required' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!config.username || typeof config.username !== 'string') {
|
|
15
|
+
return { isValid: false, error: 'ClickHouse username is required' }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!config.password || typeof config.password !== 'string') {
|
|
19
|
+
return { isValid: false, error: 'ClickHouse password is required' }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { isValid: true }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ClickHouseConfig {
|
|
26
|
+
url?: string
|
|
27
|
+
username?: string
|
|
28
|
+
password?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const generateClickHouseFetcher = (
|
|
32
|
+
config: Record<string, unknown>,
|
|
33
|
+
tableName: string
|
|
34
|
+
): string => {
|
|
35
|
+
const clickConfig = config as ClickHouseConfig
|
|
36
|
+
const url = clickConfig.url
|
|
37
|
+
const username = clickConfig.username
|
|
38
|
+
const password = clickConfig.password
|
|
39
|
+
|
|
40
|
+
return `import { createClient } from '@clickhouse/client'
|
|
41
|
+
|
|
42
|
+
let client = null
|
|
43
|
+
|
|
44
|
+
const getClient = () => {
|
|
45
|
+
if (client) return client
|
|
46
|
+
|
|
47
|
+
client = createClient({
|
|
48
|
+
url: ${JSON.stringify(url)},
|
|
49
|
+
username: ${JSON.stringify(username)},
|
|
50
|
+
password: ${replaceSecretReference(password)}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return client
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default async function handler(req, res) {
|
|
57
|
+
try {
|
|
58
|
+
const client = getClient()
|
|
59
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
60
|
+
|
|
61
|
+
const conditions = []
|
|
62
|
+
|
|
63
|
+
if (query && queryColumns) {
|
|
64
|
+
const columns = JSON.parse(queryColumns)
|
|
65
|
+
const searchConditions = columns.map(
|
|
66
|
+
(col) => \`positionCaseInsensitive(toString(\${col}), '\${query}') > 0\`
|
|
67
|
+
)
|
|
68
|
+
conditions.push(\`(\${searchConditions.join(' OR ')})\`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (filters) {
|
|
72
|
+
const parsedFilters = JSON.parse(filters)
|
|
73
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
const formattedValues = value
|
|
76
|
+
.map((v) => (typeof v === 'string' ? \`'\${v}'\` : v))
|
|
77
|
+
.join(', ')
|
|
78
|
+
conditions.push(\`\${key} IN (\${formattedValues})\`)
|
|
79
|
+
} else if (typeof value === 'string') {
|
|
80
|
+
conditions.push(\`\${key} = '\${value}'\`)
|
|
81
|
+
} else {
|
|
82
|
+
conditions.push(\`\${key} = \${value}\`)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let sql = \`SELECT * FROM ${tableName}\`
|
|
88
|
+
|
|
89
|
+
if (conditions.length > 0) {
|
|
90
|
+
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (sortBy) {
|
|
94
|
+
sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const limitValue = limit || perPage
|
|
98
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
99
|
+
|
|
100
|
+
if (limitValue) {
|
|
101
|
+
sql += \` LIMIT \${limitValue}\`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (offsetValue !== undefined) {
|
|
105
|
+
sql += \` OFFSET \${offsetValue}\`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const result = await client.query({ query: sql })
|
|
109
|
+
const resultResponse = await result.json()
|
|
110
|
+
const safeData = JSON.parse(JSON.stringify(resultResponse.data))
|
|
111
|
+
|
|
112
|
+
return res.status(200).json({
|
|
113
|
+
success: true,
|
|
114
|
+
data: safeData,
|
|
115
|
+
timestamp: Date.now()
|
|
116
|
+
})
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('ClickHouse fetch error:', error)
|
|
119
|
+
return res.status(500).json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: error.message || 'Failed to fetch data',
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`
|
|
127
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export const validateCSVConfig = (
|
|
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.parsedData || !Array.isArray(config.parsedData)) {
|
|
9
|
+
return { isValid: false, error: 'Parsed data must be an array' }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Columns are optional - if not provided, we'll infer them from parsedData
|
|
13
|
+
if (config.columns !== undefined) {
|
|
14
|
+
if (!Array.isArray(config.columns)) {
|
|
15
|
+
return { isValid: false, error: 'Columns definition must be an array' }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const column of config.columns) {
|
|
19
|
+
if (!column || typeof column !== 'object' || !column.id || typeof column.id !== 'string') {
|
|
20
|
+
return { isValid: false, error: 'Each column must have a valid id' }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { isValid: true }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CSVFileConfig {
|
|
29
|
+
parsedData?: unknown[]
|
|
30
|
+
columns?: Array<{ id: string; [key: string]: unknown }>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const generateCSVFileFetcher = (config: Record<string, unknown>): string => {
|
|
34
|
+
const csvConfig = config as CSVFileConfig
|
|
35
|
+
return `const data = ${JSON.stringify(csvConfig.parsedData || [])}
|
|
36
|
+
|
|
37
|
+
export default async function handler(req, res) {
|
|
38
|
+
try {
|
|
39
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
|
|
40
|
+
|
|
41
|
+
let filteredData = [...data]
|
|
42
|
+
|
|
43
|
+
if (query) {
|
|
44
|
+
const searchQuery = query.toLowerCase()
|
|
45
|
+
|
|
46
|
+
if (queryColumns) {
|
|
47
|
+
const columns = JSON.parse(queryColumns)
|
|
48
|
+
filteredData = filteredData.filter((item) => {
|
|
49
|
+
return columns.some((col) => {
|
|
50
|
+
const value = item[col]
|
|
51
|
+
return value && String(value).toLowerCase().includes(searchQuery)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
} else {
|
|
55
|
+
filteredData = filteredData.filter((item) => {
|
|
56
|
+
try {
|
|
57
|
+
const stringified = JSON.stringify(item).toLowerCase()
|
|
58
|
+
return stringified.includes(searchQuery)
|
|
59
|
+
} catch {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (filters) {
|
|
67
|
+
const parsedFilters = JSON.parse(filters)
|
|
68
|
+
filteredData = filteredData.filter((item) => {
|
|
69
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.includes(item[key])
|
|
72
|
+
}
|
|
73
|
+
return item[key] === value
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (sortBy) {
|
|
79
|
+
filteredData.sort((a, b) => {
|
|
80
|
+
const aVal = a[sortBy]
|
|
81
|
+
const bVal = b[sortBy]
|
|
82
|
+
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
83
|
+
if (aVal < bVal) return -sortOrderValue
|
|
84
|
+
if (aVal > bVal) return sortOrderValue
|
|
85
|
+
return 0
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const limitValue = limit || perPage
|
|
90
|
+
const offsetValue = offsetParam !== undefined ? parseInt(offsetParam) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
|
|
91
|
+
|
|
92
|
+
if (limitValue) {
|
|
93
|
+
filteredData = filteredData.slice(offsetValue, offsetValue + parseInt(limitValue))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const safeData = JSON.parse(JSON.stringify(filteredData))
|
|
97
|
+
|
|
98
|
+
return res.status(200).json({
|
|
99
|
+
success: true,
|
|
100
|
+
data: safeData,
|
|
101
|
+
timestamp: Date.now()
|
|
102
|
+
})
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('CSV fetch error:', error)
|
|
105
|
+
return res.status(500).json({
|
|
106
|
+
success: false,
|
|
107
|
+
error: error.message || 'Failed to fetch data',
|
|
108
|
+
timestamp: Date.now()
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// tslint:disable-next-line:variable-name
|
|
116
|
+
export const generateCSVCountFetcher = (_config: any): string => {
|
|
117
|
+
return `
|
|
118
|
+
async function getCount(req, res) {
|
|
119
|
+
try {
|
|
120
|
+
const { query, queryColumns, filters } = req.query
|
|
121
|
+
const fakeReq = { query: { query, queryColumns, filters }, method: 'GET' }
|
|
122
|
+
let result = null
|
|
123
|
+
let statusCode = 200
|
|
124
|
+
|
|
125
|
+
const fakeRes = {
|
|
126
|
+
status: (code) => {
|
|
127
|
+
statusCode = code
|
|
128
|
+
return fakeRes
|
|
129
|
+
},
|
|
130
|
+
json: (data) => {
|
|
131
|
+
result = data
|
|
132
|
+
return fakeRes
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await handler(fakeReq, fakeRes)
|
|
137
|
+
|
|
138
|
+
if (statusCode !== 200 || !result || !result.success) {
|
|
139
|
+
return res.status(500).json({
|
|
140
|
+
success: false,
|
|
141
|
+
error: 'Failed to get data for counting',
|
|
142
|
+
timestamp: Date.now()
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const count = Array.isArray(result.data) ? result.data.length : 0
|
|
147
|
+
|
|
148
|
+
return res.status(200).json({
|
|
149
|
+
success: true,
|
|
150
|
+
count: count,
|
|
151
|
+
timestamp: Date.now()
|
|
152
|
+
})
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Error getting count:', error)
|
|
155
|
+
return res.status(500).json({
|
|
156
|
+
success: false,
|
|
157
|
+
error: error.message || 'Failed to get count',
|
|
158
|
+
timestamp: Date.now()
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
export const validateFirestoreConfig = (
|
|
4
|
+
config: Record<string, unknown>
|
|
5
|
+
): { isValid: boolean; error?: string } => {
|
|
6
|
+
if (!config || typeof config !== 'object') {
|
|
7
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!config.serviceAccount || typeof config.serviceAccount !== 'string') {
|
|
11
|
+
return { isValid: false, error: 'Firestore service account JSON is required' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const serviceAccount = config.serviceAccount as string
|
|
15
|
+
|
|
16
|
+
// If serviceAccount is a secret reference, we assume the runtime env var will contain valid JSON
|
|
17
|
+
// Example: "teleporthq.secrets.DATA_SOURCE_FIRESTORE_SERVICE_ACCOUNT"
|
|
18
|
+
if (!serviceAccount.startsWith('teleporthq.secrets.')) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(serviceAccount)
|
|
21
|
+
if (!parsed.project_id || !parsed.private_key || !parsed.client_email) {
|
|
22
|
+
return { isValid: false, error: 'Invalid Firestore service account JSON structure' }
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
return { isValid: false, error: 'Service account must be valid JSON' }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { isValid: true }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FirestoreConfig {
|
|
33
|
+
serviceAccount?: string
|
|
34
|
+
selectedTables?: Record<string, unknown>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const generateFirestoreFetcher = (
|
|
38
|
+
config: Record<string, unknown>,
|
|
39
|
+
tableName: string
|
|
40
|
+
): string => {
|
|
41
|
+
const firestoreConfig = config as FirestoreConfig
|
|
42
|
+
const serviceAccount = firestoreConfig.serviceAccount
|
|
43
|
+
|
|
44
|
+
return `import * as admin from 'firebase-admin'
|
|
45
|
+
|
|
46
|
+
let firestore = null
|
|
47
|
+
|
|
48
|
+
const getFirestore = () => {
|
|
49
|
+
if (firestore) return firestore
|
|
50
|
+
|
|
51
|
+
const rawServiceAccount = ${replaceSecretReference(serviceAccount)}
|
|
52
|
+
let serviceAccount
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
serviceAccount = JSON.parse(rawServiceAccount)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error('Invalid Firestore service account JSON: ' + error.message)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!admin.apps.length) {
|
|
61
|
+
admin.initializeApp({
|
|
62
|
+
credential: admin.credential.cert(serviceAccount)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
firestore = admin.firestore()
|
|
67
|
+
return firestore
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default async function handler(req, res) {
|
|
71
|
+
try {
|
|
72
|
+
const firestore = getFirestore()
|
|
73
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
74
|
+
|
|
75
|
+
let queryRef = firestore.collection('${tableName}')
|
|
76
|
+
|
|
77
|
+
if (filters) {
|
|
78
|
+
const parsedFilters = JSON.parse(filters)
|
|
79
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
queryRef = queryRef.where(key, 'in', value)
|
|
82
|
+
} else {
|
|
83
|
+
queryRef = queryRef.where(key, '==', value)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (query && queryColumns) {
|
|
89
|
+
const columns = JSON.parse(queryColumns)
|
|
90
|
+
for (const column of columns) {
|
|
91
|
+
queryRef = queryRef
|
|
92
|
+
.where(column, '>=', query)
|
|
93
|
+
.where(column, '<=', query + '\\uf8ff')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (sortBy) {
|
|
98
|
+
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
99
|
+
queryRef = queryRef.orderBy(sortBy, sortOrderValue)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const limitValue = limit || perPage
|
|
103
|
+
if (limitValue) {
|
|
104
|
+
queryRef = queryRef.limit(parseInt(limitValue))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage && parseInt(page) > 1 ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
108
|
+
if (offsetValue !== undefined) {
|
|
109
|
+
queryRef = queryRef.offset(offsetValue)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const snapshot = await queryRef.get()
|
|
113
|
+
const documents = []
|
|
114
|
+
snapshot.forEach((doc) => {
|
|
115
|
+
documents.push({
|
|
116
|
+
id: doc.id,
|
|
117
|
+
...doc.data()
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const safeData = JSON.parse(JSON.stringify(documents))
|
|
122
|
+
|
|
123
|
+
return res.status(200).json({
|
|
124
|
+
success: true,
|
|
125
|
+
data: safeData,
|
|
126
|
+
timestamp: Date.now()
|
|
127
|
+
})
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Firestore fetch error:', error)
|
|
130
|
+
return res.status(500).json({
|
|
131
|
+
success: false,
|
|
132
|
+
error: error.message || 'Failed to fetch data',
|
|
133
|
+
timestamp: Date.now()
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`
|
|
138
|
+
}
|