@rapidd/build 1.0.0
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/LICENSE +21 -0
- package/README.md +86 -0
- package/bin/cli.js +30 -0
- package/index.js +11 -0
- package/package.json +63 -0
- package/src/commands/build.js +448 -0
- package/src/generators/modelGenerator.js +168 -0
- package/src/generators/relationshipsGenerator.js +186 -0
- package/src/generators/rlsGenerator.js +334 -0
- package/src/generators/rlsGeneratorV2.js +381 -0
- package/src/generators/routeGenerator.js +127 -0
- package/src/parsers/advancedRLSConverter.js +305 -0
- package/src/parsers/autoRLSConverter.js +322 -0
- package/src/parsers/datasourceParser.js +73 -0
- package/src/parsers/deepSQLAnalyzer.js +540 -0
- package/src/parsers/dynamicRLSConverter.js +353 -0
- package/src/parsers/enhancedRLSConverter.js +181 -0
- package/src/parsers/functionAnalyzer.js +302 -0
- package/src/parsers/postgresRLSConverter.js +192 -0
- package/src/parsers/prismaFilterBuilder.js +422 -0
- package/src/parsers/prismaParser.js +245 -0
- package/src/parsers/sqlToJsConverter.js +611 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic PostgreSQL RLS to JavaScript/Prisma Converter
|
|
3
|
+
* Handles any PostgreSQL RLS pattern without hardcoding assumptions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Main converter function - PostgreSQL RLS to JavaScript
|
|
8
|
+
*/
|
|
9
|
+
function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
|
|
10
|
+
if (!sql || sql.trim() === '') {
|
|
11
|
+
return 'true';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
sql = sql.trim();
|
|
15
|
+
|
|
16
|
+
// Normalize whitespace
|
|
17
|
+
sql = sql.replace(/\s+/g, ' ').replace(/\n/g, ' ');
|
|
18
|
+
|
|
19
|
+
// Handle CASE WHEN expressions
|
|
20
|
+
if (sql.toUpperCase().startsWith('CASE')) {
|
|
21
|
+
return convertCaseWhen(sql, dataVar, userVar);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle OR conditions
|
|
25
|
+
const orMatch = sql.match(/\((.*?)\)\s+OR\s+\((.*?)\)/i);
|
|
26
|
+
if (orMatch) {
|
|
27
|
+
const left = convertToJavaScript(orMatch[1], dataVar, userVar);
|
|
28
|
+
const right = convertToJavaScript(orMatch[2], dataVar, userVar);
|
|
29
|
+
return `(${left} || ${right})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle AND conditions
|
|
33
|
+
const andMatch = sql.match(/\((.*?)\)\s+AND\s+\((.*?)\)/i);
|
|
34
|
+
if (andMatch) {
|
|
35
|
+
const left = convertToJavaScript(andMatch[1], dataVar, userVar);
|
|
36
|
+
const right = convertToJavaScript(andMatch[2], dataVar, userVar);
|
|
37
|
+
return `(${left} && ${right})`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle function calls with = ANY (ARRAY[...])
|
|
41
|
+
const anyArrayMatch = sql.match(/(\w+)\s*\([^)]*\)[^=]*=\s*ANY\s*\(\s*(?:\()?ARRAY\s*\[([^\]]+)\]/i);
|
|
42
|
+
if (anyArrayMatch) {
|
|
43
|
+
const funcName = anyArrayMatch[1];
|
|
44
|
+
const arrayValues = extractArrayValues(anyArrayMatch[2]);
|
|
45
|
+
|
|
46
|
+
// Map function names to user properties dynamically
|
|
47
|
+
const userProperty = mapFunctionToUserProperty(funcName);
|
|
48
|
+
return `[${arrayValues}].includes(${userVar}?.${userProperty})`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle field = function() comparisons
|
|
52
|
+
const funcCompareMatch = sql.match(/(\w+)\s*=\s*(\w+)\s*\([^)]*\)/i);
|
|
53
|
+
if (funcCompareMatch) {
|
|
54
|
+
const field = funcCompareMatch[1];
|
|
55
|
+
const funcName = funcCompareMatch[2];
|
|
56
|
+
|
|
57
|
+
// Map function to user property
|
|
58
|
+
const userProperty = mapFunctionToUserProperty(funcName);
|
|
59
|
+
return `${dataVar}?.${field} === ${userVar}?.${userProperty}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle field = (current_setting(...))
|
|
63
|
+
const currentSettingMatch = sql.match(/(\w+)\s*=\s*\(\s*current_setting\s*\(\s*'([^']+)'/i);
|
|
64
|
+
if (currentSettingMatch) {
|
|
65
|
+
const field = currentSettingMatch[1];
|
|
66
|
+
const setting = currentSettingMatch[2];
|
|
67
|
+
|
|
68
|
+
// Map setting to user property
|
|
69
|
+
const userProperty = mapSettingToUserProperty(setting);
|
|
70
|
+
return `${dataVar}?.${field} === ${userVar}?.${userProperty}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handle EXISTS subqueries
|
|
74
|
+
if (sql.includes('EXISTS')) {
|
|
75
|
+
return handleExistsSubquery(sql, dataVar, userVar);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle simple boolean values
|
|
79
|
+
if (sql.toLowerCase() === 'true') return 'true';
|
|
80
|
+
if (sql.toLowerCase() === 'false') return 'false';
|
|
81
|
+
|
|
82
|
+
// Unhandled pattern
|
|
83
|
+
return `true /* Unhandled RLS pattern: ${sql.substring(0, 60)}... */`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Map PostgreSQL function names to user object properties
|
|
88
|
+
* This is where customization happens based on your PostgreSQL functions
|
|
89
|
+
*/
|
|
90
|
+
function mapFunctionToUserProperty(funcName) {
|
|
91
|
+
const mappings = {
|
|
92
|
+
// Common patterns - can be customized per project
|
|
93
|
+
'get_current_user_role': 'role',
|
|
94
|
+
'get_current_user_id': 'id',
|
|
95
|
+
'current_user_id': 'id',
|
|
96
|
+
'get_current_teacher_id': 'teacher_id',
|
|
97
|
+
'get_current_student_id': 'student_id',
|
|
98
|
+
'get_current_agency_id': 'agency_id',
|
|
99
|
+
'get_current_school_id': 'school_id',
|
|
100
|
+
// Add more mappings as needed
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// If we have a mapping, use it
|
|
104
|
+
if (mappings[funcName]) {
|
|
105
|
+
return mappings[funcName];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Try to infer from function name
|
|
109
|
+
// get_current_X_id -> X_id
|
|
110
|
+
const getIdMatch = funcName.match(/get_current_(\w+)_id/i);
|
|
111
|
+
if (getIdMatch) {
|
|
112
|
+
return `${getIdMatch[1]}_id`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// get_current_X -> X
|
|
116
|
+
const getCurrentMatch = funcName.match(/get_current_(\w+)/i);
|
|
117
|
+
if (getCurrentMatch) {
|
|
118
|
+
return getCurrentMatch[1];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// current_X -> X
|
|
122
|
+
const currentMatch = funcName.match(/current_(\w+)/i);
|
|
123
|
+
if (currentMatch) {
|
|
124
|
+
return currentMatch[1];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Default: use function name as-is
|
|
128
|
+
return funcName;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Map PostgreSQL settings to user properties
|
|
133
|
+
*/
|
|
134
|
+
function mapSettingToUserProperty(setting) {
|
|
135
|
+
// app.current_user_id -> id
|
|
136
|
+
if (setting === 'app.current_user_id') {
|
|
137
|
+
return 'id';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// app.current_X -> X
|
|
141
|
+
const appMatch = setting.match(/app\.current_(\w+)/i);
|
|
142
|
+
if (appMatch) {
|
|
143
|
+
return appMatch[1];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Default
|
|
147
|
+
return setting.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extract and format array values
|
|
152
|
+
*/
|
|
153
|
+
function extractArrayValues(arrayContent) {
|
|
154
|
+
return arrayContent
|
|
155
|
+
.split(',')
|
|
156
|
+
.map(r => r.trim())
|
|
157
|
+
.map(r => r.replace(/::[^,\]]+/g, '')) // Remove all type casts
|
|
158
|
+
.map(r => r.replace(/^'|'$/g, '')) // Remove quotes
|
|
159
|
+
.map(r => `'${r}'`)
|
|
160
|
+
.join(', ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Convert CASE WHEN expressions
|
|
165
|
+
*/
|
|
166
|
+
function convertCaseWhen(sql, dataVar, userVar) {
|
|
167
|
+
const caseMatch = sql.match(/CASE\s+(\S+(?:\([^)]*\))?)\s+((?:WHEN[\s\S]+?)+)\s*(?:ELSE\s+([\s\S]+?))?\s*END/i);
|
|
168
|
+
|
|
169
|
+
if (!caseMatch) {
|
|
170
|
+
return `false /* Unparseable CASE expression */`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const caseExpr = caseMatch[1];
|
|
174
|
+
const whenClauses = caseMatch[2];
|
|
175
|
+
const elseClause = caseMatch[3];
|
|
176
|
+
|
|
177
|
+
// Extract the function being called in CASE
|
|
178
|
+
let testProperty = 'unknown';
|
|
179
|
+
const funcMatch = caseExpr.match(/(\w+)\s*\(/);
|
|
180
|
+
if (funcMatch) {
|
|
181
|
+
testProperty = mapFunctionToUserProperty(funcMatch[1]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parse WHEN branches
|
|
185
|
+
const whenPattern = /WHEN\s+'?([^':\s]+)'?(?:::\w+)?\s+THEN\s+((?:(?!WHEN\s+|ELSE\s+|END).)+)/gi;
|
|
186
|
+
const conditions = [];
|
|
187
|
+
|
|
188
|
+
let match;
|
|
189
|
+
while ((match = whenPattern.exec(whenClauses)) !== null) {
|
|
190
|
+
const value = match[1];
|
|
191
|
+
const thenExpr = match[2].trim();
|
|
192
|
+
|
|
193
|
+
if (thenExpr.toLowerCase() === 'true') {
|
|
194
|
+
conditions.push(`${userVar}?.${testProperty} === '${value}'`);
|
|
195
|
+
} else if (thenExpr.toLowerCase() === 'false') {
|
|
196
|
+
// Skip false conditions
|
|
197
|
+
} else {
|
|
198
|
+
// Complex THEN expression - recursively convert
|
|
199
|
+
const convertedThen = convertToJavaScript(thenExpr, dataVar, userVar);
|
|
200
|
+
if (convertedThen !== 'false') {
|
|
201
|
+
conditions.push(`(${userVar}?.${testProperty} === '${value}' && ${convertedThen})`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Handle ELSE clause
|
|
207
|
+
if (elseClause && elseClause.trim().toLowerCase() !== 'false') {
|
|
208
|
+
const elseConverted = convertToJavaScript(elseClause.trim(), dataVar, userVar);
|
|
209
|
+
if (elseConverted !== 'false') {
|
|
210
|
+
conditions.push(elseConverted);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return conditions.length > 0 ? `(${conditions.join(' || ')})` : 'false';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Handle EXISTS subqueries
|
|
219
|
+
* These typically check relationships and need manual implementation
|
|
220
|
+
*/
|
|
221
|
+
function handleExistsSubquery(sql, dataVar, userVar) {
|
|
222
|
+
// Try to extract meaningful information from the EXISTS clause
|
|
223
|
+
const existsMatch = sql.match(/EXISTS\s*\(\s*SELECT[^)]+FROM\s+(\w+)[^)]+WHERE\s+([^)]+)\)/i);
|
|
224
|
+
|
|
225
|
+
if (existsMatch) {
|
|
226
|
+
const tableName = existsMatch[1];
|
|
227
|
+
const whereClause = existsMatch[2];
|
|
228
|
+
|
|
229
|
+
// Look for common patterns in WHERE clause
|
|
230
|
+
// Pattern: checking if related table's foreign key matches current context
|
|
231
|
+
const fkMatch = whereClause.match(/(\w+)\.(\w+)\s*=\s*(\w+)\.(\w+)/gi);
|
|
232
|
+
if (fkMatch) {
|
|
233
|
+
return `true /* TODO: Check ${tableName} relationship via JOIN */`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Pattern: checking against current user functions
|
|
237
|
+
if (whereClause.match(/get_current_\w+_id\(\)/i)) {
|
|
238
|
+
return `true /* TODO: Verify ${tableName} access for current user context */`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return `true /* EXISTS subquery needs manual implementation */`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Convert to Prisma filter
|
|
247
|
+
*/
|
|
248
|
+
function convertToPrismaFilter(sql, userVar = 'user') {
|
|
249
|
+
if (!sql || sql.trim() === '') {
|
|
250
|
+
return '{}';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
sql = sql.trim().replace(/\s+/g, ' ').replace(/\n/g, ' ');
|
|
254
|
+
|
|
255
|
+
// CASE WHEN - extract filterable conditions
|
|
256
|
+
if (sql.toUpperCase().startsWith('CASE')) {
|
|
257
|
+
return extractPrismaFiltersFromCase(sql, userVar);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Handle field = function() patterns
|
|
261
|
+
const funcCompareMatch = sql.match(/(\w+)\s*=\s*(\w+)\s*\([^)]*\)/i);
|
|
262
|
+
if (funcCompareMatch) {
|
|
263
|
+
const field = funcCompareMatch[1];
|
|
264
|
+
const funcName = funcCompareMatch[2];
|
|
265
|
+
const userProperty = mapFunctionToUserProperty(funcName);
|
|
266
|
+
|
|
267
|
+
// Only create filter if it's a data field comparison
|
|
268
|
+
if (!funcName.includes('role')) {
|
|
269
|
+
return `{ ${field}: ${userVar}?.${userProperty} }`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Handle current_setting patterns
|
|
274
|
+
const settingMatch = sql.match(/(\w+)\s*=\s*\(\s*current_setting\s*\(\s*'([^']+)'/i);
|
|
275
|
+
if (settingMatch) {
|
|
276
|
+
const field = settingMatch[1];
|
|
277
|
+
const setting = settingMatch[2];
|
|
278
|
+
const userProperty = mapSettingToUserProperty(setting);
|
|
279
|
+
return `{ ${field}: ${userVar}?.${userProperty} }`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Handle OR conditions
|
|
283
|
+
const orMatch = sql.match(/\((.*?)\)\s+OR\s+\((.*?)\)/i);
|
|
284
|
+
if (orMatch) {
|
|
285
|
+
const left = convertToPrismaFilter(orMatch[1], userVar);
|
|
286
|
+
const right = convertToPrismaFilter(orMatch[2], userVar);
|
|
287
|
+
|
|
288
|
+
if (left !== '{}' && right !== '{}') {
|
|
289
|
+
return `{ OR: [${left}, ${right}] }`;
|
|
290
|
+
} else if (left !== '{}') {
|
|
291
|
+
return left;
|
|
292
|
+
} else if (right !== '{}') {
|
|
293
|
+
return right;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Role-based checks can't be filtered in Prisma
|
|
298
|
+
if (sql.match(/get_current_\w+_role/i) || sql.includes('= ANY')) {
|
|
299
|
+
return '{}';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return '{}';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Extract Prisma filters from CASE WHEN
|
|
307
|
+
*/
|
|
308
|
+
function extractPrismaFiltersFromCase(sql, userVar) {
|
|
309
|
+
const filters = new Set();
|
|
310
|
+
|
|
311
|
+
// Look for all field = function() patterns in the CASE statement
|
|
312
|
+
const fieldMatches = sql.matchAll(/(\w+)\s*=\s*(\w+)\s*\([^)]*\)/gi);
|
|
313
|
+
|
|
314
|
+
for (const match of fieldMatches) {
|
|
315
|
+
const field = match[1];
|
|
316
|
+
const funcName = match[2];
|
|
317
|
+
|
|
318
|
+
// Skip role checks
|
|
319
|
+
if (funcName.includes('role')) continue;
|
|
320
|
+
|
|
321
|
+
const userProperty = mapFunctionToUserProperty(funcName);
|
|
322
|
+
filters.add(`{ ${field}: ${userVar}?.${userProperty} }`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Look for current_setting patterns
|
|
326
|
+
const settingMatches = sql.matchAll(/(\w+)\s*=\s*\(\s*current_setting\s*\(\s*'([^']+)'/gi);
|
|
327
|
+
|
|
328
|
+
for (const match of settingMatches) {
|
|
329
|
+
const field = match[1];
|
|
330
|
+
const setting = match[2];
|
|
331
|
+
const userProperty = mapSettingToUserProperty(setting);
|
|
332
|
+
filters.add(`{ ${field}: ${userVar}?.${userProperty} }`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const filterArray = Array.from(filters);
|
|
336
|
+
|
|
337
|
+
if (filterArray.length === 0) {
|
|
338
|
+
return '{}';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (filterArray.length === 1) {
|
|
342
|
+
return filterArray[0];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return `{ OR: [${filterArray.join(', ')}] }`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
convertToJavaScript,
|
|
350
|
+
convertToPrismaFilter,
|
|
351
|
+
mapFunctionToUserProperty,
|
|
352
|
+
mapSettingToUserProperty
|
|
353
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced RLS Converter using Deep SQL Analysis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const DeepSQLAnalyzer = require('./deepSQLAnalyzer');
|
|
6
|
+
const PrismaFilterBuilder = require('./prismaFilterBuilder');
|
|
7
|
+
|
|
8
|
+
function createEnhancedConverter(functionMappings = {}, sessionVariables = {}, models = {}, relationships = {}) {
|
|
9
|
+
const analyzer = new DeepSQLAnalyzer();
|
|
10
|
+
const filterBuilder = new PrismaFilterBuilder(models, relationships);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert PostgreSQL RLS to JavaScript with deep analysis
|
|
14
|
+
*/
|
|
15
|
+
function convertToJavaScript(sql, dataVar = 'data', userVar = 'user', modelName = null) {
|
|
16
|
+
if (!sql || sql.trim() === '') {
|
|
17
|
+
return 'true';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Use deep analysis
|
|
21
|
+
const analysis = analyzer.analyzeSQLForFilters(sql);
|
|
22
|
+
analysis.sql = sql; // Store original SQL for OR/AND detection
|
|
23
|
+
|
|
24
|
+
// If we have models and relationships, use the filter builder for better JavaScript
|
|
25
|
+
if (modelName && Object.keys(models).length > 0) {
|
|
26
|
+
return filterBuilder.buildJavaScriptCondition(modelName, analysis, dataVar, userVar);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Fallback to simple JavaScript generation
|
|
30
|
+
const conditions = [];
|
|
31
|
+
|
|
32
|
+
// Add filter-based conditions
|
|
33
|
+
for (const filter of analysis.filters) {
|
|
34
|
+
if (filter.type.startsWith('user_') || filter.type.startsWith('session_')) {
|
|
35
|
+
// Dynamic user field comparison
|
|
36
|
+
const userField = filter.userField || filter.type.replace(/^(user_|session_)/, '');
|
|
37
|
+
conditions.push(`${dataVar}?.${filter.field} === ${userVar}?.${userField}`);
|
|
38
|
+
} else {
|
|
39
|
+
switch (filter.type) {
|
|
40
|
+
case 'equal':
|
|
41
|
+
if (isNaN(filter.value) && filter.value !== 'true' && filter.value !== 'false') {
|
|
42
|
+
conditions.push(`${dataVar}?.${filter.field} === '${filter.value}'`);
|
|
43
|
+
} else {
|
|
44
|
+
conditions.push(`${dataVar}?.${filter.field} === ${filter.value}`);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case 'not_equal':
|
|
48
|
+
conditions.push(`${dataVar}?.${filter.field} !== '${filter.value}'`);
|
|
49
|
+
break;
|
|
50
|
+
case 'is_null':
|
|
51
|
+
conditions.push(`${dataVar}?.${filter.field} === null`);
|
|
52
|
+
break;
|
|
53
|
+
case 'not_null':
|
|
54
|
+
conditions.push(`${dataVar}?.${filter.field} !== null`);
|
|
55
|
+
break;
|
|
56
|
+
case 'in':
|
|
57
|
+
conditions.push(`[${filter.values.join(', ')}].includes(${dataVar}?.${filter.field})`);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add condition-based checks (roles, etc.)
|
|
64
|
+
for (const condition of analysis.conditions) {
|
|
65
|
+
if (condition.javascript) {
|
|
66
|
+
// Replace user placeholder with actual variable
|
|
67
|
+
const jsCondition = condition.javascript.replace(/user/g, userVar);
|
|
68
|
+
conditions.push(jsCondition);
|
|
69
|
+
} else if (condition.type === 'role_any') {
|
|
70
|
+
conditions.push(`[${condition.roles.map(r => `'${r}'`).join(', ')}].includes(${userVar}?.role)`);
|
|
71
|
+
} else if (condition.type === 'role_equal') {
|
|
72
|
+
conditions.push(`${userVar}?.role === '${condition.role}'`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle OR/AND logic in original SQL
|
|
77
|
+
sql = sql.trim().replace(/\s+/g, ' ');
|
|
78
|
+
|
|
79
|
+
// Check if the entire expression is OR'd
|
|
80
|
+
if (sql.includes(' OR ') && !sql.includes(' AND ')) {
|
|
81
|
+
// If we have multiple conditions and SQL uses OR, join with ||
|
|
82
|
+
if (conditions.length > 1) {
|
|
83
|
+
return conditions.join(' || ');
|
|
84
|
+
}
|
|
85
|
+
} else if (sql.includes(' AND ') && conditions.length > 1) {
|
|
86
|
+
// If SQL uses AND, wrap in parentheses for clarity
|
|
87
|
+
return '(' + conditions.join(' && ') + ')';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Default: join with AND
|
|
91
|
+
return conditions.length > 0 ? conditions.join(' && ') : 'true';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert to Prisma filter with deep analysis
|
|
96
|
+
*/
|
|
97
|
+
function convertToPrismaFilter(sql, userVar = 'user', modelName = null) {
|
|
98
|
+
if (!sql || sql.trim() === '') return '{}';
|
|
99
|
+
|
|
100
|
+
// Use deep analysis
|
|
101
|
+
const analysis = analyzer.analyzeSQLForFilters(sql);
|
|
102
|
+
analysis.sql = sql; // Store original SQL for OR/AND detection
|
|
103
|
+
|
|
104
|
+
// If we have models and relationships, use the filter builder
|
|
105
|
+
if (modelName && Object.keys(models).length > 0) {
|
|
106
|
+
return filterBuilder.buildFilter(modelName, analysis, userVar);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fallback to simple filter generation
|
|
110
|
+
const filters = [];
|
|
111
|
+
|
|
112
|
+
for (const filter of analysis.filters) {
|
|
113
|
+
if (filter.type.startsWith('user_') || filter.type.startsWith('session_')) {
|
|
114
|
+
// Dynamic user field comparison
|
|
115
|
+
const userField = filter.userField || filter.type.replace(/^(user_|session_)/, '');
|
|
116
|
+
filters.push(`{ ${filter.field}: ${userVar}?.${userField} }`);
|
|
117
|
+
} else {
|
|
118
|
+
switch (filter.type) {
|
|
119
|
+
case 'equal':
|
|
120
|
+
if (isNaN(filter.value) && filter.value !== 'true' && filter.value !== 'false') {
|
|
121
|
+
filters.push(`{ ${filter.field}: '${filter.value}' }`);
|
|
122
|
+
} else {
|
|
123
|
+
filters.push(`{ ${filter.field}: ${filter.value} }`);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case 'not_equal':
|
|
127
|
+
filters.push(`{ ${filter.field}: { not: '${filter.value}' } }`);
|
|
128
|
+
break;
|
|
129
|
+
case 'is_null':
|
|
130
|
+
filters.push(`{ ${filter.field}: null }`);
|
|
131
|
+
break;
|
|
132
|
+
case 'not_null':
|
|
133
|
+
filters.push(`{ ${filter.field}: { not: null } }`);
|
|
134
|
+
break;
|
|
135
|
+
case 'in':
|
|
136
|
+
filters.push(`{ ${filter.field}: { in: [${filter.values.join(', ')}] } }`);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Role checks can't be directly filtered in Prisma (they're runtime checks)
|
|
143
|
+
// But we can still return the data filters
|
|
144
|
+
|
|
145
|
+
if (filters.length === 0) {
|
|
146
|
+
return '{}';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (filters.length === 1) {
|
|
150
|
+
return filters[0];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if original SQL uses OR or AND
|
|
154
|
+
sql = sql.trim().replace(/\s+/g, ' ');
|
|
155
|
+
|
|
156
|
+
if (sql.includes(' OR ') && !sql.includes(' AND ')) {
|
|
157
|
+
// Use OR for multiple filters
|
|
158
|
+
return `{ OR: [${filters.join(', ')}] }`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Default to AND
|
|
162
|
+
return `{ AND: [${filters.join(', ')}] }`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Analyze and get user context requirements
|
|
167
|
+
*/
|
|
168
|
+
function getUserContextRequirements(sql) {
|
|
169
|
+
const analysis = analyzer.analyzeSQLForFilters(sql);
|
|
170
|
+
return analysis.userContext || {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
convertToJavaScript,
|
|
175
|
+
convertToPrismaFilter,
|
|
176
|
+
getUserContextRequirements,
|
|
177
|
+
analyzer // Expose analyzer for debugging
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { createEnhancedConverter };
|