@rapidd/build 1.1.0 → 1.1.1
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/README.md +2 -5
- package/package.json +1 -1
- package/src/commands/build.js +1 -1
- package/src/generators/aclGenerator.js +0 -1
- package/src/generators/relationshipsGenerator.js +25 -26
- package/src/generators/routeGenerator.js +1 -1
- package/src/parsers/prismaParser.js +11 -1
- package/src/parsers/advancedRLSConverter.js +0 -305
- package/src/parsers/autoRLSConverter.js +0 -322
- package/src/parsers/dynamicRLSConverter.js +0 -379
- package/src/parsers/postgresRLSConverter.js +0 -192
- package/src/parsers/sqlToJsConverter.js +0 -611
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Dynamic code generator that transforms Prisma schemas into complete Express.js C
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- 🚀 **Automatic CRUD API Generation** - Creates Express.js routes from Prisma models
|
|
8
|
-
- 🔒 **RLS Translation** - Converts PostgreSQL Row-Level Security policies to JavaScript/Prisma filters (ACL)
|
|
8
|
+
- 🔒 **RLS Translation** - Converts PostgreSQL Row-Level Security policies to JavaScript/Prisma filters (ACL: Access Control Layer)
|
|
9
9
|
- 🎯 **Dynamic & Schema-Aware** - Zero hardcoding, adapts to any database structure
|
|
10
10
|
- 🔗 **Relationship Handling** - Supports 1:1, 1:n, n:m including junction tables
|
|
11
11
|
- 👥 **Role-Based Access Control** - Properly handles role checks in filters
|
|
@@ -50,7 +50,7 @@ npx rapidd build --user-table accounts
|
|
|
50
50
|
- `-s, --schema <path>` - Prisma schema file (default: `./prisma/schema.prisma`)
|
|
51
51
|
- `-m, --model <name>` - Generate/update only specific model (e.g., "account", "user")
|
|
52
52
|
- `--only <component>` - Generate only specific component: "model", "route", "acl", or "relationship"
|
|
53
|
-
- `--user-table <name>` - User table name for
|
|
53
|
+
- `--user-table <name>` - User table name for ACL (default: auto-detected)
|
|
54
54
|
|
|
55
55
|
## Selective Generation
|
|
56
56
|
|
|
@@ -122,9 +122,6 @@ CREATE POLICY user_policy ON posts
|
|
|
122
122
|
|
|
123
123
|
**Generated JavaScript:**
|
|
124
124
|
```javascript
|
|
125
|
-
hasAccess: (data, user) => {
|
|
126
|
-
return data?.author_id === user?.id || ['admin', 'moderator'].includes(user?.role);
|
|
127
|
-
},
|
|
128
125
|
getAccessFilter: (user) => {
|
|
129
126
|
if (['admin', 'moderator'].includes(user?.role)) return {};
|
|
130
127
|
return { author_id: user?.id };
|
package/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { Client } = require('pg');
|
|
4
|
-
const { createConverter } = require('../parsers/autoRLSConverter');
|
|
5
4
|
const { createEnhancedConverter } = require('../parsers/enhancedRLSConverter');
|
|
6
5
|
const { analyzeFunctions, generateMappingConfig } = require('../parsers/functionAnalyzer');
|
|
7
6
|
|
|
@@ -48,9 +48,15 @@ function generateRelationships(models, outputPath) {
|
|
|
48
48
|
// Find the foreign key field name
|
|
49
49
|
const foreignKeyField = findForeignKeyField(relation, modelInfo, relatedModel);
|
|
50
50
|
|
|
51
|
+
if (!foreignKeyField) {
|
|
52
|
+
// Skip this relationship if we can't find the FK field
|
|
53
|
+
// This usually means the FK is on the other side of the relation
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
relationships[modelName][relation.name] = {
|
|
52
58
|
'object': relation.type,
|
|
53
|
-
'field': foreignKeyField
|
|
59
|
+
'field': foreignKeyField
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -74,39 +80,32 @@ function generateRelationships(models, outputPath) {
|
|
|
74
80
|
* @returns {string|null} - Foreign key field name
|
|
75
81
|
*/
|
|
76
82
|
function findForeignKeyField(relation, currentModel, relatedModel) {
|
|
77
|
-
//
|
|
78
|
-
if (relation.relationFromFields && relation.relationFromFields.length > 0) {
|
|
79
|
-
return relation.relationFromFields[0];
|
|
80
|
-
}
|
|
83
|
+
// IMPORTANT: The foreign key field is where the @relation(fields: [...]) is defined
|
|
81
84
|
|
|
82
|
-
// For array relations (one-to-many
|
|
85
|
+
// For array relations (one-to-many), the FK is in the related model (child)
|
|
83
86
|
if (relation.isArray) {
|
|
84
|
-
// Find
|
|
85
|
-
for (const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
// Find the corresponding relation in the related model that points back
|
|
88
|
+
for (const relField of Object.values(relatedModel.fields)) {
|
|
89
|
+
if (relField.kind === 'object' &&
|
|
90
|
+
relField.relationName === relation.relationName &&
|
|
91
|
+
relField.relationFromFields &&
|
|
92
|
+
relField.relationFromFields.length > 0) {
|
|
93
|
+
// This is the FK field in the child (related) model
|
|
94
|
+
return relField.relationFromFields[0];
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return `${relation.type}_id`;
|
|
97
|
+
// Fallback
|
|
98
|
+
return null;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
// For singular relations (many-to-one),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
fieldInfo.relationToFields.length > 0) {
|
|
103
|
-
// Found the matching relation field, return its FK
|
|
104
|
-
return fieldName;
|
|
105
|
-
}
|
|
101
|
+
// For singular relations (many-to-one or one-to-one), check if THIS relation has fields defined
|
|
102
|
+
if (relation.relationFromFields && relation.relationFromFields.length > 0) {
|
|
103
|
+
// The FK is in the current model
|
|
104
|
+
return relation.relationFromFields[0];
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
//
|
|
109
|
-
return
|
|
107
|
+
// If no fields on this side, the FK must be on the other side (shouldn't use this relation for filtering)
|
|
108
|
+
return null;
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
/**
|
|
@@ -27,7 +27,7 @@ router.get('/', async function(req, res) {
|
|
|
27
27
|
try {
|
|
28
28
|
const { q = {}, include = "", limit = 25, offset = 0, sortBy = "id", sortOrder = "asc" } = req.query;
|
|
29
29
|
const results = await req.${className}.getMany(q, include, limit, offset, sortBy, sortOrder);
|
|
30
|
-
return res.sendList(results.data,
|
|
30
|
+
return res.sendList(results.data, results.meta);
|
|
31
31
|
}
|
|
32
32
|
catch(error){
|
|
33
33
|
const response = QueryBuilder.errorHandler(error);
|
|
@@ -248,8 +248,18 @@ async function parsePrismaDMMF(prismaClientPath) {
|
|
|
248
248
|
name: field.name,
|
|
249
249
|
type: field.type,
|
|
250
250
|
isArray: field.isList,
|
|
251
|
-
optional: !field.isRequired
|
|
251
|
+
optional: !field.isRequired,
|
|
252
|
+
relationName: field.relationName,
|
|
253
|
+
relationFromFields: field.relationFromFields || [],
|
|
254
|
+
relationToFields: field.relationToFields || [],
|
|
255
|
+
kind: field.kind
|
|
252
256
|
});
|
|
257
|
+
|
|
258
|
+
// Also add these to the field object for consistency
|
|
259
|
+
models[model.name].fields[field.name].relationName = field.relationName;
|
|
260
|
+
models[model.name].fields[field.name].relationFromFields = field.relationFromFields || [];
|
|
261
|
+
models[model.name].fields[field.name].relationToFields = field.relationToFields || [];
|
|
262
|
+
models[model.name].fields[field.name].kind = field.kind;
|
|
253
263
|
}
|
|
254
264
|
}
|
|
255
265
|
}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advanced PostgreSQL RLS to JavaScript/Prisma Converter
|
|
3
|
-
* Handles real production PostgreSQL RLS patterns with custom functions
|
|
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
|
-
// Remove extra whitespace and newlines for consistent parsing
|
|
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 combined conditions with OR
|
|
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 role checks: get_current_user_role() = ANY (ARRAY[...])
|
|
33
|
-
if (sql.includes('get_current_user_role')) {
|
|
34
|
-
return convertRoleCheck(sql, userVar);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Handle field comparisons with custom functions
|
|
38
|
-
const customFuncPatterns = [
|
|
39
|
-
{ pattern: /teacher_id\s*=\s*get_current_teacher_id\(\)/i, replacement: `${dataVar}?.teacher_id === ${userVar}?.teacher_id` },
|
|
40
|
-
{ pattern: /student_id\s*=\s*get_current_student_id\(\)/i, replacement: `${dataVar}?.student_id === ${userVar}?.student_id` },
|
|
41
|
-
{ pattern: /user_id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'[^)]*\)[^)]*\)/i, replacement: `${dataVar}?.user_id === ${userVar}?.id` },
|
|
42
|
-
{ pattern: /id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'[^)]*\)[^)]*\)/i, replacement: `${dataVar}?.id === ${userVar}?.id` },
|
|
43
|
-
{ pattern: /(\w+)\s*=\s*get_current_teacher_id\(\)/i, replacement: (m) => `${dataVar}?.${m[1]} === ${userVar}?.teacher_id` },
|
|
44
|
-
{ pattern: /(\w+)\s*=\s*get_current_student_id\(\)/i, replacement: (m) => `${dataVar}?.${m[1]} === ${userVar}?.student_id` }
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
for (const { pattern, replacement } of customFuncPatterns) {
|
|
48
|
-
if (pattern.test(sql)) {
|
|
49
|
-
if (typeof replacement === 'function') {
|
|
50
|
-
const match = sql.match(pattern);
|
|
51
|
-
return replacement(match);
|
|
52
|
-
}
|
|
53
|
-
return replacement;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Handle EXISTS subqueries
|
|
58
|
-
if (sql.includes('EXISTS')) {
|
|
59
|
-
return convertExistsSubquery(sql, dataVar, userVar);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Fallback for unhandled patterns
|
|
63
|
-
return `true /* Complex RLS expression: ${sql.substring(0, 80)}... */`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Convert role check expressions
|
|
68
|
-
*/
|
|
69
|
-
function convertRoleCheck(sql, userVar) {
|
|
70
|
-
// Extract roles from ARRAY[...] pattern
|
|
71
|
-
const arrayMatch = sql.match(/ARRAY\s*\[([^\]]+)\]/i);
|
|
72
|
-
if (arrayMatch) {
|
|
73
|
-
const roles = arrayMatch[1]
|
|
74
|
-
.split(',')
|
|
75
|
-
.map(r => r.trim())
|
|
76
|
-
.map(r => r.replace(/::[^,\]]+/g, '')) // Remove type casts (including "character varying")
|
|
77
|
-
.map(r => r.replace(/^'|'$/g, '')) // Remove quotes
|
|
78
|
-
.map(r => `'${r}'`)
|
|
79
|
-
.join(', ');
|
|
80
|
-
return `[${roles}].includes(${userVar}?.role)`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Simple role equality
|
|
84
|
-
const simpleMatch = sql.match(/get_current_user_role\(\)[^=]*=\s*'([^']+)'/i);
|
|
85
|
-
if (simpleMatch) {
|
|
86
|
-
return `${userVar}?.role === '${simpleMatch[1]}'`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return `true /* Unparseable role check */`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Convert CASE WHEN expressions
|
|
94
|
-
*/
|
|
95
|
-
function convertCaseWhen(sql, dataVar, userVar) {
|
|
96
|
-
// Extract the CASE expression and branches
|
|
97
|
-
const caseMatch = sql.match(/CASE\s+(\S+(?:\([^)]*\))?)\s+((?:WHEN[\s\S]+?)+)\s*(?:ELSE\s+([\s\S]+?))?\s*END/i);
|
|
98
|
-
|
|
99
|
-
if (!caseMatch) {
|
|
100
|
-
return `false /* Unparseable CASE expression */`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const caseExpr = caseMatch[1];
|
|
104
|
-
const whenClauses = caseMatch[2];
|
|
105
|
-
const elseClause = caseMatch[3];
|
|
106
|
-
|
|
107
|
-
// Determine what's being tested
|
|
108
|
-
let testExpr = '';
|
|
109
|
-
if (caseExpr.includes('get_current_user_role')) {
|
|
110
|
-
testExpr = `${userVar}?.role`;
|
|
111
|
-
} else if (caseExpr.includes('get_current_teacher_id')) {
|
|
112
|
-
testExpr = `${userVar}?.teacher_id`;
|
|
113
|
-
} else if (caseExpr.includes('get_current_student_id')) {
|
|
114
|
-
testExpr = `${userVar}?.student_id`;
|
|
115
|
-
} else {
|
|
116
|
-
return `false /* Unsupported CASE expression: ${caseExpr} */`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Parse WHEN branches
|
|
120
|
-
const whenPattern = /WHEN\s+'?([^':\s]+)'?(?:::\w+)?\s+THEN\s+((?:(?!WHEN\s+|ELSE\s+|END).)+)/gi;
|
|
121
|
-
const conditions = [];
|
|
122
|
-
|
|
123
|
-
let match;
|
|
124
|
-
while ((match = whenPattern.exec(whenClauses)) !== null) {
|
|
125
|
-
const value = match[1];
|
|
126
|
-
const thenExpr = match[2].trim();
|
|
127
|
-
|
|
128
|
-
if (thenExpr.toLowerCase() === 'true') {
|
|
129
|
-
conditions.push(`${testExpr} === '${value}'`);
|
|
130
|
-
} else if (thenExpr.toLowerCase() === 'false') {
|
|
131
|
-
// Skip false conditions
|
|
132
|
-
} else {
|
|
133
|
-
// Complex THEN expression
|
|
134
|
-
const convertedThen = convertThenExpression(thenExpr, dataVar, userVar);
|
|
135
|
-
if (convertedThen !== 'false') {
|
|
136
|
-
conditions.push(`(${testExpr} === '${value}' && ${convertedThen})`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Handle ELSE clause
|
|
142
|
-
if (elseClause && elseClause.trim().toLowerCase() === 'true') {
|
|
143
|
-
conditions.push('true');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return conditions.length > 0 ? conditions.join(' || ') : 'false';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Convert THEN expressions in CASE WHEN
|
|
151
|
-
*/
|
|
152
|
-
function convertThenExpression(expr, dataVar, userVar) {
|
|
153
|
-
expr = expr.trim();
|
|
154
|
-
|
|
155
|
-
// Remove parentheses
|
|
156
|
-
if (expr.startsWith('(') && expr.endsWith(')')) {
|
|
157
|
-
expr = expr.substring(1, expr.length - 1).trim();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Handle EXISTS
|
|
161
|
-
if (expr.includes('EXISTS')) {
|
|
162
|
-
return convertExistsSubquery(expr, dataVar, userVar);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Handle field = function() patterns
|
|
166
|
-
const patterns = [
|
|
167
|
-
{ regex: /teacher_id\s*=\s*get_current_teacher_id\(\)/i, js: `${dataVar}?.teacher_id === ${userVar}?.teacher_id` },
|
|
168
|
-
{ regex: /student_id\s*=\s*get_current_student_id\(\)/i, js: `${dataVar}?.student_id === ${userVar}?.student_id` },
|
|
169
|
-
{ regex: /id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'[^)]*\)[^)]*\)/i, js: `${dataVar}?.id === ${userVar}?.id` },
|
|
170
|
-
{ regex: /(\w+)\s*=\s*get_current_(\w+)_id\(\)/i, handler: (m) => `${dataVar}?.${m[1]} === ${userVar}?.${m[2]}_id` }
|
|
171
|
-
];
|
|
172
|
-
|
|
173
|
-
for (const pattern of patterns) {
|
|
174
|
-
if (pattern.regex.test(expr)) {
|
|
175
|
-
if (pattern.handler) {
|
|
176
|
-
const match = expr.match(pattern.regex);
|
|
177
|
-
return pattern.handler(match);
|
|
178
|
-
}
|
|
179
|
-
return pattern.js;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return 'true';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Convert EXISTS subqueries to JavaScript
|
|
188
|
-
* These typically check relationships between tables
|
|
189
|
-
*/
|
|
190
|
-
function convertExistsSubquery(sql, dataVar, userVar) {
|
|
191
|
-
// Common patterns in EXISTS subqueries
|
|
192
|
-
|
|
193
|
-
// Pattern 1: Check if student_tariff belongs to current student
|
|
194
|
-
if (sql.includes('student_tariff') && sql.includes('get_current_student_id')) {
|
|
195
|
-
return `true /* TODO: Check if ${dataVar}?.student_tariff.student_id === ${userVar}?.student_id */`;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Pattern 2: Check if teacher has access
|
|
199
|
-
if (sql.includes('get_current_teacher_id')) {
|
|
200
|
-
return `true /* TODO: Check if ${dataVar} is accessible to teacher ${userVar}?.teacher_id */`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Pattern 3: Check user address relationships
|
|
204
|
-
if (sql.includes('contact_address_id') || sql.includes('billing_address_id')) {
|
|
205
|
-
return `true /* TODO: Check if address belongs to user via contact_address_id or billing_address_id */`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Pattern 4: Generic student access check
|
|
209
|
-
if (sql.includes('student') && sql.includes('get_current_student_id')) {
|
|
210
|
-
return `true /* TODO: Check student relationship */`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Default EXISTS handling
|
|
214
|
-
return `true /* EXISTS subquery requires manual implementation */`;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Convert to Prisma filter
|
|
219
|
-
*/
|
|
220
|
-
function convertToPrismaFilter(sql, userVar = 'user') {
|
|
221
|
-
if (!sql || sql.trim() === '') {
|
|
222
|
-
return '{}';
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
sql = sql.trim().replace(/\s+/g, ' ').replace(/\n/g, ' ');
|
|
226
|
-
|
|
227
|
-
// Role-based filters can't be directly applied in Prisma WHERE clause
|
|
228
|
-
if (sql.includes('get_current_user_role')) {
|
|
229
|
-
return '{}'; // Role checks are done in hasAccess, not in filter
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Handle CASE WHEN - extract filterable conditions
|
|
233
|
-
if (sql.toUpperCase().startsWith('CASE')) {
|
|
234
|
-
return convertCaseWhenToPrisma(sql, userVar);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Handle simple field comparisons
|
|
238
|
-
const patterns = [
|
|
239
|
-
{ regex: /teacher_id\s*=\s*get_current_teacher_id\(\)/i, filter: `{ teacher_id: ${userVar}?.teacher_id }` },
|
|
240
|
-
{ regex: /student_id\s*=\s*get_current_student_id\(\)/i, filter: `{ student_id: ${userVar}?.student_id }` },
|
|
241
|
-
{ regex: /user_id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'/i, filter: `{ user_id: ${userVar}?.id }` },
|
|
242
|
-
{ regex: /^id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'/i, filter: `{ id: ${userVar}?.id }` }
|
|
243
|
-
];
|
|
244
|
-
|
|
245
|
-
for (const pattern of patterns) {
|
|
246
|
-
if (pattern.regex.test(sql)) {
|
|
247
|
-
return pattern.filter;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Handle OR conditions
|
|
252
|
-
const orMatch = sql.match(/\((.*?)\)\s+OR\s+\((.*?)\)/i);
|
|
253
|
-
if (orMatch) {
|
|
254
|
-
const left = convertToPrismaFilter(orMatch[1], userVar);
|
|
255
|
-
const right = convertToPrismaFilter(orMatch[2], userVar);
|
|
256
|
-
if (left !== '{}' && right !== '{}') {
|
|
257
|
-
return `{ OR: [${left}, ${right}] }`;
|
|
258
|
-
} else if (left !== '{}') {
|
|
259
|
-
return left;
|
|
260
|
-
} else if (right !== '{}') {
|
|
261
|
-
return right;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return '{}';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Convert CASE WHEN to Prisma filter
|
|
270
|
-
*/
|
|
271
|
-
function convertCaseWhenToPrisma(sql, userVar) {
|
|
272
|
-
// Extract field-based conditions that can be filtered in Prisma
|
|
273
|
-
const filters = [];
|
|
274
|
-
|
|
275
|
-
// Look for teacher_id = get_current_teacher_id()
|
|
276
|
-
if (sql.includes('teacher_id') && sql.includes('get_current_teacher_id')) {
|
|
277
|
-
filters.push(`{ teacher_id: ${userVar}?.teacher_id }`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Look for student_id = get_current_student_id()
|
|
281
|
-
if (sql.includes('student_id') && sql.includes('get_current_student_id')) {
|
|
282
|
-
filters.push(`{ student_id: ${userVar}?.student_id }`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Look for id = current_setting('app.current_user_id')
|
|
286
|
-
if (sql.match(/id\s*=\s*\(\s*current_setting\s*\(\s*'app\.current_user_id'/i)) {
|
|
287
|
-
filters.push(`{ id: ${userVar}?.id }`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (filters.length === 0) {
|
|
291
|
-
return '{}';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (filters.length === 1) {
|
|
295
|
-
return filters[0];
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Multiple conditions are OR'd in CASE WHEN
|
|
299
|
-
return `{ OR: [${filters.join(', ')}] }`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
module.exports = {
|
|
303
|
-
convertToJavaScript,
|
|
304
|
-
convertToPrismaFilter
|
|
305
|
-
};
|