@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,239 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
export const validateMongoDBConfig = (
|
|
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 connectionString is provided, validate it
|
|
11
|
+
if (config.connectionString) {
|
|
12
|
+
if (typeof config.connectionString !== 'string' || config.connectionString.trim() === '') {
|
|
13
|
+
return { isValid: false, error: 'Connection string must be a non-empty string' }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Only validate format if it's not a secret reference that will be resolved at runtime
|
|
17
|
+
const connStr = config.connectionString as string
|
|
18
|
+
if (
|
|
19
|
+
!connStr.startsWith('teleporthq.secrets.') &&
|
|
20
|
+
!connStr.startsWith('mongodb://') &&
|
|
21
|
+
!connStr.startsWith('mongodb+srv://')
|
|
22
|
+
) {
|
|
23
|
+
return { isValid: false, error: 'Invalid MongoDB connection string format' }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { isValid: true }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If no connectionString, host/port/database/etc will be used to build one
|
|
30
|
+
// Make host optional - if neither connectionString nor host is provided,
|
|
31
|
+
// the generator will need to handle it
|
|
32
|
+
if (config.host !== undefined && typeof config.host !== 'string') {
|
|
33
|
+
return { isValid: false, error: 'Host must be a string' }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!config.database || typeof config.database !== 'string') {
|
|
37
|
+
return { isValid: false, error: 'Database name is required' }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { isValid: true }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface MongoDBConfig {
|
|
44
|
+
connectionString?: string
|
|
45
|
+
host?: string
|
|
46
|
+
port?: number
|
|
47
|
+
username?: string
|
|
48
|
+
password?: string
|
|
49
|
+
database?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const generateMongoDBFetcher = (
|
|
53
|
+
config: Record<string, unknown>,
|
|
54
|
+
tableName: string
|
|
55
|
+
): string => {
|
|
56
|
+
const mongoConfig = config as MongoDBConfig
|
|
57
|
+
const hasUsername = mongoConfig?.username
|
|
58
|
+
const database = mongoConfig?.database
|
|
59
|
+
|
|
60
|
+
// Build connection string from parts if not provided
|
|
61
|
+
let connectionString = mongoConfig.connectionString
|
|
62
|
+
if (!connectionString) {
|
|
63
|
+
connectionString = `mongodb://${
|
|
64
|
+
hasUsername ? `${mongoConfig.username}:${mongoConfig.password}@` : ''
|
|
65
|
+
}${mongoConfig.host}:${mongoConfig.port || 27017}/${database}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return `import { MongoClient, ObjectId } from 'mongodb'
|
|
69
|
+
|
|
70
|
+
export default async function handler(req, res) {
|
|
71
|
+
let client = null
|
|
72
|
+
try {
|
|
73
|
+
const url = ${replaceSecretReference(connectionString)}
|
|
74
|
+
client = new MongoClient(url, {
|
|
75
|
+
connectTimeoutMS: 30000,
|
|
76
|
+
serverSelectionTimeoutMS: 30000
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
await client.connect()
|
|
80
|
+
const db = client.db(${JSON.stringify(database)})
|
|
81
|
+
const collection = db.collection('${tableName}')
|
|
82
|
+
|
|
83
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
84
|
+
|
|
85
|
+
const filter = {}
|
|
86
|
+
|
|
87
|
+
if (query) {
|
|
88
|
+
let columns = []
|
|
89
|
+
|
|
90
|
+
if (queryColumns) {
|
|
91
|
+
// Use specified columns
|
|
92
|
+
columns = JSON.parse(queryColumns)
|
|
93
|
+
} else {
|
|
94
|
+
// Fallback: Get all field names from a sample document
|
|
95
|
+
try {
|
|
96
|
+
const sampleDoc = await db.collection(${JSON.stringify(tableName)}).findOne({})
|
|
97
|
+
if (sampleDoc) {
|
|
98
|
+
columns = Object.keys(sampleDoc).filter(key => key !== '_id')
|
|
99
|
+
}
|
|
100
|
+
} catch (schemaError) {
|
|
101
|
+
console.warn('Failed to fetch sample document for column names:', schemaError.message)
|
|
102
|
+
// Continue without search if we can't get columns
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (columns.length > 0) {
|
|
107
|
+
const orConditions = columns.map((col) => ({
|
|
108
|
+
[col]: { $regex: query, $options: 'i' }
|
|
109
|
+
}))
|
|
110
|
+
filter.$or = orConditions
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (filters) {
|
|
115
|
+
const parsedFilters = JSON.parse(filters)
|
|
116
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
117
|
+
if (key === '_id') {
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
filter[key] = {
|
|
120
|
+
$in: value.map((id) => (typeof id === 'string' ? new ObjectId(id) : id))
|
|
121
|
+
}
|
|
122
|
+
} else if (typeof value === 'string') {
|
|
123
|
+
filter[key] = new ObjectId(value)
|
|
124
|
+
} else {
|
|
125
|
+
filter[key] = value
|
|
126
|
+
}
|
|
127
|
+
} else if (Array.isArray(value)) {
|
|
128
|
+
filter[key] = { $in: value }
|
|
129
|
+
} else {
|
|
130
|
+
filter[key] = value
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let cursor = collection.find(filter)
|
|
136
|
+
|
|
137
|
+
if (sortBy) {
|
|
138
|
+
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
139
|
+
cursor = cursor.sort({ [sortBy]: sortOrderValue })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const limitValue = limit || perPage
|
|
143
|
+
const skipValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
144
|
+
|
|
145
|
+
if (skipValue !== undefined) {
|
|
146
|
+
cursor = cursor.skip(skipValue)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (limitValue) {
|
|
150
|
+
cursor = cursor.limit(parseInt(limitValue))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const documents = await cursor.toArray()
|
|
154
|
+
const safeData = JSON.parse(JSON.stringify(documents))
|
|
155
|
+
|
|
156
|
+
return res.status(200).json({
|
|
157
|
+
success: true,
|
|
158
|
+
data: safeData,
|
|
159
|
+
timestamp: Date.now()
|
|
160
|
+
})
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('MongoDB fetch error:', error)
|
|
163
|
+
return res.status(500).json({
|
|
164
|
+
success: false,
|
|
165
|
+
error: error.message || 'Failed to fetch data',
|
|
166
|
+
timestamp: Date.now()
|
|
167
|
+
})
|
|
168
|
+
} finally {
|
|
169
|
+
if (client) {
|
|
170
|
+
await client.close()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// tslint:disable-next-line:variable-name
|
|
178
|
+
export const generateMongoDBCountFetcher = (_config: any, tableName: string): string => {
|
|
179
|
+
return `
|
|
180
|
+
async function getCount(req, res) {
|
|
181
|
+
const client = getClient()
|
|
182
|
+
const db = client.db()
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const { query, queryColumns, filters } = req.query
|
|
186
|
+
const collection = db.collection('${tableName}')
|
|
187
|
+
const filter = {}
|
|
188
|
+
|
|
189
|
+
if (query) {
|
|
190
|
+
let columns = []
|
|
191
|
+
|
|
192
|
+
if (queryColumns) {
|
|
193
|
+
// Use specified columns
|
|
194
|
+
columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
|
|
195
|
+
} else {
|
|
196
|
+
// Fallback: Get all field names from a sample document
|
|
197
|
+
try {
|
|
198
|
+
const sampleDoc = await collection.findOne({})
|
|
199
|
+
if (sampleDoc) {
|
|
200
|
+
columns = Object.keys(sampleDoc).filter(key => key !== '_id')
|
|
201
|
+
}
|
|
202
|
+
} catch (schemaError) {
|
|
203
|
+
console.warn('Failed to fetch sample document for column names:', schemaError.message)
|
|
204
|
+
// Continue without search if we can't get columns
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (columns.length > 0) {
|
|
209
|
+
filter.$or = columns.map(col => ({
|
|
210
|
+
[col]: { $regex: query, $options: 'i' }
|
|
211
|
+
}))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (filters) {
|
|
216
|
+
const parsedFilters = JSON.parse(filters)
|
|
217
|
+
for (const f of parsedFilters) {
|
|
218
|
+
filter[f.column] = f.value
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const count = await collection.countDocuments(filter)
|
|
223
|
+
|
|
224
|
+
return res.status(200).json({
|
|
225
|
+
success: true,
|
|
226
|
+
count: count,
|
|
227
|
+
timestamp: Date.now()
|
|
228
|
+
})
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('Error getting count:', error)
|
|
231
|
+
return res.status(500).json({
|
|
232
|
+
success: false,
|
|
233
|
+
error: error.message || 'Failed to get count',
|
|
234
|
+
timestamp: Date.now()
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
`
|
|
239
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
interface MySQLConfig {
|
|
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 generateMySQLFetcher = (
|
|
15
|
+
config: Record<string, unknown>,
|
|
16
|
+
tableName: string
|
|
17
|
+
): string => {
|
|
18
|
+
const mysqlConfig = config as MySQLConfig
|
|
19
|
+
const resolvedUser = mysqlConfig.user || mysqlConfig.username || null
|
|
20
|
+
const hasCustomSSLConfig = !!mysqlConfig.sslConfig
|
|
21
|
+
const defaultSSLEnabled = mysqlConfig.ssl !== false
|
|
22
|
+
const database = mysqlConfig.database
|
|
23
|
+
|
|
24
|
+
const sslConfigString = hasCustomSSLConfig
|
|
25
|
+
? `{
|
|
26
|
+
${mysqlConfig.sslConfig.ca ? `ca: ${replaceSecretReference(mysqlConfig.sslConfig.ca)},` : ''}
|
|
27
|
+
${
|
|
28
|
+
mysqlConfig.sslConfig.cert
|
|
29
|
+
? `cert: ${replaceSecretReference(mysqlConfig.sslConfig.cert)},`
|
|
30
|
+
: ''
|
|
31
|
+
}
|
|
32
|
+
${
|
|
33
|
+
mysqlConfig.sslConfig.key
|
|
34
|
+
? `key: ${replaceSecretReference(mysqlConfig.sslConfig.key)},`
|
|
35
|
+
: ''
|
|
36
|
+
}
|
|
37
|
+
rejectUnauthorized: ${
|
|
38
|
+
mysqlConfig.sslConfig.rejectUnauthorized !== undefined
|
|
39
|
+
? mysqlConfig.sslConfig.rejectUnauthorized
|
|
40
|
+
: true
|
|
41
|
+
}
|
|
42
|
+
}`
|
|
43
|
+
: defaultSSLEnabled
|
|
44
|
+
? `{ rejectUnauthorized: true }`
|
|
45
|
+
: 'false'
|
|
46
|
+
|
|
47
|
+
return `import mysql from 'mysql2/promise'
|
|
48
|
+
|
|
49
|
+
let pool = null
|
|
50
|
+
|
|
51
|
+
const getPool = () => {
|
|
52
|
+
if (pool) return pool
|
|
53
|
+
|
|
54
|
+
pool = mysql.createPool({
|
|
55
|
+
host: ${JSON.stringify(mysqlConfig.host)},
|
|
56
|
+
port: ${mysqlConfig.port || 3306},
|
|
57
|
+
user: ${resolvedUser !== null ? JSON.stringify(resolvedUser) : 'undefined'},
|
|
58
|
+
password: ${replaceSecretReference(mysqlConfig.password)},
|
|
59
|
+
database: ${JSON.stringify(mysqlConfig.database)},
|
|
60
|
+
ssl: ${sslConfigString}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return pool
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default async function handler(req, res) {
|
|
67
|
+
try {
|
|
68
|
+
const pool = getPool()
|
|
69
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
70
|
+
|
|
71
|
+
const conditions = []
|
|
72
|
+
const queryParams = []
|
|
73
|
+
|
|
74
|
+
if (query) {
|
|
75
|
+
let columns = []
|
|
76
|
+
|
|
77
|
+
if (queryColumns) {
|
|
78
|
+
// Use specified columns
|
|
79
|
+
columns = JSON.parse(queryColumns)
|
|
80
|
+
} else {
|
|
81
|
+
// Fallback: Get all columns from information_schema
|
|
82
|
+
try {
|
|
83
|
+
const [schemaRows] = await pool.promise().query(
|
|
84
|
+
\`SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
|
85
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
86
|
+
ORDER BY ORDINAL_POSITION\`,
|
|
87
|
+
[${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
|
|
88
|
+
)
|
|
89
|
+
columns = schemaRows.map(row => row.COLUMN_NAME)
|
|
90
|
+
} catch (schemaError) {
|
|
91
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
92
|
+
// Continue without search if we can't get columns
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (columns.length > 0) {
|
|
97
|
+
const searchConditions = columns.map((col) => \`CAST(\${mysql.escapeId(col)} AS CHAR) LIKE ?\`)
|
|
98
|
+
columns.forEach(() => queryParams.push(\`%\${query}%\`))
|
|
99
|
+
conditions.push(\`(\${searchConditions.join(' OR ')})\`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (filters) {
|
|
104
|
+
const parsedFilters = JSON.parse(filters)
|
|
105
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
const placeholders = value.map(() => '?').join(', ')
|
|
108
|
+
queryParams.push(...value)
|
|
109
|
+
conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
|
|
110
|
+
} else {
|
|
111
|
+
conditions.push(\`\${mysql.escapeId(key)} = ?\`)
|
|
112
|
+
queryParams.push(value)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let sql = \`SELECT * FROM \${mysql.escapeId('${tableName}')}\`
|
|
118
|
+
|
|
119
|
+
if (conditions.length > 0) {
|
|
120
|
+
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (sortBy) {
|
|
124
|
+
sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const limitValue = limit || perPage
|
|
128
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
129
|
+
|
|
130
|
+
if (limitValue) {
|
|
131
|
+
sql += \` LIMIT \${limitValue}\`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (offsetValue !== undefined) {
|
|
135
|
+
sql += \` OFFSET \${offsetValue}\`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const [rows] = await pool.query(sql, queryParams)
|
|
139
|
+
const rowArray = Array.isArray(rows) ? rows : []
|
|
140
|
+
const plainRows = rowArray.map((row) =>
|
|
141
|
+
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
142
|
+
)
|
|
143
|
+
const safeData = JSON.parse(JSON.stringify(plainRows))
|
|
144
|
+
|
|
145
|
+
return res.status(200).json({
|
|
146
|
+
success: true,
|
|
147
|
+
data: safeData,
|
|
148
|
+
timestamp: Date.now()
|
|
149
|
+
})
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('MySQL fetch error:', error)
|
|
152
|
+
return res.status(500).json({
|
|
153
|
+
success: false,
|
|
154
|
+
error: error.message || 'Failed to fetch data',
|
|
155
|
+
timestamp: Date.now()
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const generateMySQLCountFetcher = (
|
|
163
|
+
config: Record<string, unknown>,
|
|
164
|
+
tableName: string
|
|
165
|
+
): string => {
|
|
166
|
+
const mysqlConfig = config as MySQLConfig
|
|
167
|
+
|
|
168
|
+
return `
|
|
169
|
+
async function getCount(req, res) {
|
|
170
|
+
const connection = getConnection()
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const { query, queryColumns, filters } = req.query
|
|
174
|
+
const conditions = []
|
|
175
|
+
const queryParams = []
|
|
176
|
+
|
|
177
|
+
if (query) {
|
|
178
|
+
let columns = []
|
|
179
|
+
|
|
180
|
+
if (queryColumns) {
|
|
181
|
+
// Use specified columns
|
|
182
|
+
columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
|
|
183
|
+
} else {
|
|
184
|
+
// Fallback: Get all columns from information_schema
|
|
185
|
+
try {
|
|
186
|
+
const [schemaRows] = await connection.execute(
|
|
187
|
+
\`SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
|
188
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
189
|
+
ORDER BY ORDINAL_POSITION\`,
|
|
190
|
+
[${JSON.stringify(mysqlConfig.database)}, ${JSON.stringify(tableName)}]
|
|
191
|
+
)
|
|
192
|
+
columns = schemaRows.map(row => row.COLUMN_NAME)
|
|
193
|
+
} catch (schemaError) {
|
|
194
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
195
|
+
// Continue without search if we can't get columns
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (columns.length > 0) {
|
|
200
|
+
const searchConditions = columns.map(col => \`CAST(\${col} AS CHAR) LIKE ?\`).join(' OR ')
|
|
201
|
+
conditions.push(\`(\${searchConditions})\`)
|
|
202
|
+
columns.forEach(() => queryParams.push(\`%\${query}%\`))
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (filters) {
|
|
207
|
+
const parsedFilters = JSON.parse(filters)
|
|
208
|
+
for (const filter of parsedFilters) {
|
|
209
|
+
conditions.push(\`\${filter.column} \${filter.operator} ?\`)
|
|
210
|
+
queryParams.push(filter.value)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
|
|
215
|
+
if (conditions.length > 0) {
|
|
216
|
+
countSql += \` WHERE \${conditions.join(' AND ')}\`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const [rows] = await connection.execute(countSql, queryParams)
|
|
220
|
+
const count = rows[0].count
|
|
221
|
+
|
|
222
|
+
return res.status(200).json({
|
|
223
|
+
success: true,
|
|
224
|
+
count: count,
|
|
225
|
+
timestamp: Date.now()
|
|
226
|
+
})
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Error getting count:', error)
|
|
229
|
+
return res.status(500).json({
|
|
230
|
+
success: false,
|
|
231
|
+
error: error.message || 'Failed to get count',
|
|
232
|
+
timestamp: Date.now()
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
`
|
|
237
|
+
}
|