@rapidd/core 2.1.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/.dockerignore +71 -0
- package/.env.example +70 -0
- package/.gitignore +11 -0
- package/LICENSE +15 -0
- package/README.md +231 -0
- package/bin/cli.js +145 -0
- package/config/app.json +166 -0
- package/config/rate-limit.json +12 -0
- package/dist/main.js +26 -0
- package/dockerfile +57 -0
- package/locales/ar_SA.json +179 -0
- package/locales/de_DE.json +179 -0
- package/locales/en_US.json +180 -0
- package/locales/es_ES.json +179 -0
- package/locales/fr_FR.json +179 -0
- package/locales/it_IT.json +179 -0
- package/locales/ja_JP.json +179 -0
- package/locales/pt_BR.json +179 -0
- package/locales/ru_RU.json +179 -0
- package/locales/tr_TR.json +179 -0
- package/main.ts +25 -0
- package/package.json +126 -0
- package/prisma/schema.prisma +9 -0
- package/prisma.config.ts +12 -0
- package/public/static/favicon.ico +0 -0
- package/public/static/image/logo.png +0 -0
- package/routes/api/v1/index.ts +113 -0
- package/src/app.ts +197 -0
- package/src/auth/Auth.ts +446 -0
- package/src/auth/stores/ISessionStore.ts +19 -0
- package/src/auth/stores/MemoryStore.ts +70 -0
- package/src/auth/stores/RedisStore.ts +92 -0
- package/src/auth/stores/index.ts +149 -0
- package/src/config/acl.ts +9 -0
- package/src/config/rls.ts +38 -0
- package/src/core/dmmf.ts +226 -0
- package/src/core/env.ts +183 -0
- package/src/core/errors.ts +87 -0
- package/src/core/i18n.ts +144 -0
- package/src/core/middleware.ts +123 -0
- package/src/core/prisma.ts +236 -0
- package/src/index.ts +112 -0
- package/src/middleware/model.ts +61 -0
- package/src/orm/Model.ts +881 -0
- package/src/orm/QueryBuilder.ts +2078 -0
- package/src/plugins/auth.ts +162 -0
- package/src/plugins/language.ts +79 -0
- package/src/plugins/rateLimit.ts +210 -0
- package/src/plugins/response.ts +80 -0
- package/src/plugins/rls.ts +51 -0
- package/src/plugins/security.ts +23 -0
- package/src/plugins/upload.ts +299 -0
- package/src/types.ts +308 -0
- package/src/utils/ApiClient.ts +526 -0
- package/src/utils/Mailer.ts +348 -0
- package/src/utils/index.ts +25 -0
- package/templates/email/example.ejs +17 -0
- package/templates/layouts/email.ejs +35 -0
- package/tsconfig.json +33 -0
package/src/orm/Model.ts
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
import { QueryBuilder } from './QueryBuilder';
|
|
2
|
+
import { prisma, prismaTransaction, getAcl } from '../core/prisma';
|
|
3
|
+
import { modelMiddleware } from '../core/middleware';
|
|
4
|
+
import { ErrorResponse } from '../core/errors';
|
|
5
|
+
import type { RapiddUser, ModelOptions, GetManyResult, UpsertManyResult, UpsertManyOptions, ModelAcl, MiddlewareContext } from '../types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base Model class for Rapidd ORM operations
|
|
9
|
+
* Provides CRUD operations with built-in ACL (Access Control List) and middleware support
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* class Users extends Model {
|
|
13
|
+
* constructor(options: ModelOptions) {
|
|
14
|
+
* super('users', options);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* const users = new Users({ user: { id: '123', role: 'admin' } });
|
|
19
|
+
* const result = await users.getMany({}, 'profile', 10, 0);
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Register middleware to auto-add timestamps
|
|
23
|
+
* Model.middleware.use('before', 'create', async (ctx: MiddlewareContext) => {
|
|
24
|
+
* ctx.data.createdAt = new Date();
|
|
25
|
+
* ctx.data.createdBy = ctx.user?.id;
|
|
26
|
+
* return ctx;
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
class Model {
|
|
30
|
+
name!: string;
|
|
31
|
+
queryBuilder: QueryBuilder;
|
|
32
|
+
acl: ModelAcl;
|
|
33
|
+
options: ModelOptions;
|
|
34
|
+
user: RapiddUser;
|
|
35
|
+
user_id: string | number | null;
|
|
36
|
+
prisma: any; // Prisma model delegate
|
|
37
|
+
|
|
38
|
+
static QueryBuilder?: QueryBuilder;
|
|
39
|
+
static relatedObjects: any[] = [];
|
|
40
|
+
static Error = ErrorResponse;
|
|
41
|
+
static middleware = modelMiddleware;
|
|
42
|
+
static prismaTransaction = prismaTransaction;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a new Model instance
|
|
46
|
+
* @param name - The Prisma model name (e.g., 'users', 'company_profiles')
|
|
47
|
+
* @param options - Configuration options
|
|
48
|
+
*/
|
|
49
|
+
constructor(name: string, options?: ModelOptions) {
|
|
50
|
+
this.name = name;
|
|
51
|
+
this.prisma = (prisma as any)[name];
|
|
52
|
+
this.queryBuilder = (this.constructor as typeof Model).QueryBuilder ?? new QueryBuilder(name);
|
|
53
|
+
const aclConfig = getAcl();
|
|
54
|
+
this.acl = aclConfig.model[name] || {};
|
|
55
|
+
this.options = options || {};
|
|
56
|
+
this.user = this.options.user || { id: 'system', role: 'application' };
|
|
57
|
+
this.user_id = this.user ? this.user.id : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the primary key field name for this model
|
|
62
|
+
* For composite keys, returns fields joined with underscore (Prisma composite key format)
|
|
63
|
+
*/
|
|
64
|
+
get primaryKey(): string {
|
|
65
|
+
const pkey = this.queryBuilder.getPrimaryKey();
|
|
66
|
+
return Array.isArray(pkey) ? pkey.join('_') : pkey;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get raw primary key field(s) from DMMF
|
|
71
|
+
* Returns string for simple PKs, string[] for composite PKs
|
|
72
|
+
*/
|
|
73
|
+
get primaryKeyFields(): string | string[] {
|
|
74
|
+
return this.queryBuilder.getPrimaryKey();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the default sort field for this model
|
|
79
|
+
* For composite keys, returns the first field
|
|
80
|
+
*/
|
|
81
|
+
get defaultSortField(): string {
|
|
82
|
+
const pk = this.primaryKeyFields;
|
|
83
|
+
return Array.isArray(pk) ? pk[0] : pk;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether this model has a composite primary key
|
|
88
|
+
*/
|
|
89
|
+
get isCompositePK(): boolean {
|
|
90
|
+
return Array.isArray(this.primaryKeyFields);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build a Prisma where clause for the given ID value(s)
|
|
95
|
+
* For simple PKs: { id: value }
|
|
96
|
+
* For composite PKs with object: { email_companyId: { email: '...', companyId: '...' } }
|
|
97
|
+
* For composite PKs with tilde-delimited string: parses "val1~val2" into fields
|
|
98
|
+
*/
|
|
99
|
+
buildWhereId(id: string | number | Record<string, any>): Record<string, any> {
|
|
100
|
+
const pkFields = this.primaryKeyFields;
|
|
101
|
+
|
|
102
|
+
if (!Array.isArray(pkFields)) {
|
|
103
|
+
// Simple PK
|
|
104
|
+
return { [pkFields]: id };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Composite PK
|
|
108
|
+
const compositeKeyName = pkFields.join('_');
|
|
109
|
+
|
|
110
|
+
if (typeof id === 'object' && id !== null) {
|
|
111
|
+
// Already an object with field values
|
|
112
|
+
return { [compositeKeyName]: id };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof id === 'string' && id.includes('~')) {
|
|
116
|
+
// Tilde-delimited string from URL
|
|
117
|
+
const parts = id.split('~');
|
|
118
|
+
if (parts.length !== pkFields.length) {
|
|
119
|
+
throw new ErrorResponse(400, "invalid_composite_key", {
|
|
120
|
+
expected: pkFields,
|
|
121
|
+
received: parts.length
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const values: Record<string, any> = {};
|
|
125
|
+
pkFields.forEach((field: string, i: number) => {
|
|
126
|
+
values[field] = this.#coercePrimaryKeyValue(field, parts[i]);
|
|
127
|
+
});
|
|
128
|
+
return { [compositeKeyName]: values };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new ErrorResponse(400, "invalid_composite_key_format", {
|
|
132
|
+
message: "Composite key requires either an object or tilde-separated string",
|
|
133
|
+
fields: pkFields
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build a Prisma where clause for a unique key (used in upsert)
|
|
139
|
+
*/
|
|
140
|
+
buildWhereUniqueKey(uniqueKey: string | string[], data: Record<string, any>): Record<string, any> {
|
|
141
|
+
if (Array.isArray(uniqueKey)) {
|
|
142
|
+
const compositeKeyName = uniqueKey.join('_');
|
|
143
|
+
const compositeKeyValues: Record<string, any> = {};
|
|
144
|
+
uniqueKey.forEach((key: string) => {
|
|
145
|
+
compositeKeyValues[key] = data[key];
|
|
146
|
+
});
|
|
147
|
+
return { [compositeKeyName]: compositeKeyValues };
|
|
148
|
+
}
|
|
149
|
+
return { [uniqueKey]: data[uniqueKey] };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Build a Prisma select clause for primary key fields
|
|
154
|
+
*/
|
|
155
|
+
#buildPrimaryKeySelect(): Record<string, true> {
|
|
156
|
+
const pkFields = this.primaryKeyFields;
|
|
157
|
+
if (!Array.isArray(pkFields)) {
|
|
158
|
+
return { [pkFields]: true };
|
|
159
|
+
}
|
|
160
|
+
return pkFields.reduce((acc: Record<string, true>, f: string) => { acc[f] = true; return acc; }, {});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Compare two records by their primary key fields
|
|
165
|
+
*/
|
|
166
|
+
#primaryKeysMatch(a: Record<string, any>, b: Record<string, any>): boolean {
|
|
167
|
+
if (!a || !b) return false;
|
|
168
|
+
const pkFields = this.primaryKeyFields;
|
|
169
|
+
const fields = Array.isArray(pkFields) ? pkFields : [pkFields];
|
|
170
|
+
return fields.every((f: string) => a[f] != null && String(a[f]) === String(b[f]));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Coerce a string PK value to the correct type based on DMMF field type
|
|
175
|
+
*/
|
|
176
|
+
#coercePrimaryKeyValue(fieldName: string, value: string): any {
|
|
177
|
+
const field = this.fields[fieldName];
|
|
178
|
+
if (!field) return value;
|
|
179
|
+
if (field.type === 'Int') return parseInt(value, 10);
|
|
180
|
+
if (field.type === 'Float' || field.type === 'Decimal') return parseFloat(value);
|
|
181
|
+
if (field.type === 'Boolean') return value === 'true';
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get all fields for this model from DMMF
|
|
187
|
+
*/
|
|
188
|
+
get fields(): Record<string, any> {
|
|
189
|
+
return this.queryBuilder.fields;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Build select clause */
|
|
193
|
+
_select = (fields?: string[] | Record<string, any> | null): Record<string, any> => this.queryBuilder.select(fields as any);
|
|
194
|
+
/** Build filter/where clause */
|
|
195
|
+
_filter = (q: any): Record<string, any> => this.queryBuilder.filter(q);
|
|
196
|
+
/** Build include clause for relations */
|
|
197
|
+
_include = (include: string | Record<string, any>): Record<string, any> => this.queryBuilder.include(include, this.user);
|
|
198
|
+
/** Transform data for create operation */
|
|
199
|
+
_queryCreate = (data: Record<string, any>): Record<string, any> => this.queryBuilder.create(data, this.user);
|
|
200
|
+
/** Transform data for update operation */
|
|
201
|
+
_queryUpdate = (id: string | number, data: Record<string, any>): Record<string, any> => this.queryBuilder.update(id, data, this.user);
|
|
202
|
+
|
|
203
|
+
// ACL METHODS
|
|
204
|
+
/** Check if user can create records */
|
|
205
|
+
_canCreate = (data?: Record<string, any>): boolean => this.acl.canCreate ? this.acl.canCreate(this.user, data) : true;
|
|
206
|
+
/** Get access filter from ACL */
|
|
207
|
+
_getAccessFilter = (): any => this.acl.getAccessFilter?.(this.user);
|
|
208
|
+
/** Get update filter from ACL */
|
|
209
|
+
_getUpdateFilter = (): any => this.acl.getUpdateFilter?.(this.user);
|
|
210
|
+
/** Get delete filter from ACL */
|
|
211
|
+
_getDeleteFilter = (): any => this.acl.getDeleteFilter?.(this.user);
|
|
212
|
+
/** Get fields to omit from response */
|
|
213
|
+
_omit = (): Record<string, boolean> | undefined => this.queryBuilder.omit(this.user) as Record<string, boolean> | undefined;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Execute middleware chain for an operation
|
|
217
|
+
*/
|
|
218
|
+
async _executeMiddleware(hook: 'before' | 'after', operation: string, params: Record<string, any>): Promise<MiddlewareContext> {
|
|
219
|
+
const context = modelMiddleware.createContext({ name: this.name }, operation, params, this.user);
|
|
220
|
+
return await modelMiddleware.execute(hook, operation as any, context);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Internal method to fetch multiple records with filtering and pagination
|
|
225
|
+
*/
|
|
226
|
+
_getMany = async (
|
|
227
|
+
q: Record<string, any> = {},
|
|
228
|
+
include: string | Record<string, any> = "",
|
|
229
|
+
limit: number = 25,
|
|
230
|
+
offset: number = 0,
|
|
231
|
+
sortBy: string = this.defaultSortField,
|
|
232
|
+
sortOrder: string = "asc",
|
|
233
|
+
options: Record<string, any> = {},
|
|
234
|
+
fields: string | null = null,
|
|
235
|
+
): Promise<GetManyResult> => {
|
|
236
|
+
const take = this.take(Number(limit));
|
|
237
|
+
const skip = this.skip(Number(offset));
|
|
238
|
+
|
|
239
|
+
sortBy = sortBy?.trim();
|
|
240
|
+
sortOrder = sortOrder?.trim();
|
|
241
|
+
|
|
242
|
+
// Validate sort field - fall back to default for composite PK names
|
|
243
|
+
if (!sortBy.includes('.') && this.fields[sortBy] == undefined) {
|
|
244
|
+
// If the sortBy is a composite key name (e.g., "email_companyId"), use first PK field
|
|
245
|
+
if (sortBy === this.primaryKey && this.isCompositePK) {
|
|
246
|
+
sortBy = this.defaultSortField;
|
|
247
|
+
} else {
|
|
248
|
+
throw new ErrorResponse(400, "invalid_sort_field", { sortBy, modelName: this.constructor.name });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Execute before middleware
|
|
253
|
+
const beforeCtx = await this._executeMiddleware('before', 'getMany', { query: q, include, take, skip, sortBy, sortOrder, options, fields });
|
|
254
|
+
|
|
255
|
+
if (beforeCtx.abort) {
|
|
256
|
+
return (beforeCtx.result as GetManyResult) || { data: [], meta: { take, skip, total: 0 } };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Build field selection (handles select vs include+omit based on fields param)
|
|
260
|
+
// Use beforeCtx.fields directly since it's always initialized from the context params
|
|
261
|
+
const fieldSelection = this.queryBuilder.buildFieldSelection(
|
|
262
|
+
beforeCtx.fields as string | null,
|
|
263
|
+
beforeCtx.include || include,
|
|
264
|
+
this.user
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Query the database using Prisma with filters, pagination, and limits
|
|
268
|
+
const [data, total] = await prismaTransaction([
|
|
269
|
+
(tx: any) => tx[this.name].findMany({
|
|
270
|
+
'where': this.filter(beforeCtx.query || q),
|
|
271
|
+
...fieldSelection,
|
|
272
|
+
'take': beforeCtx.take || take,
|
|
273
|
+
'skip': beforeCtx.skip || skip,
|
|
274
|
+
'orderBy': this.sort(beforeCtx.sortBy || sortBy, beforeCtx.sortOrder || sortOrder),
|
|
275
|
+
...(beforeCtx.options || options)
|
|
276
|
+
}),
|
|
277
|
+
(tx: any) => tx[this.name].count({
|
|
278
|
+
'where': this.filter(beforeCtx.query || q)
|
|
279
|
+
})
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
const result: GetManyResult = { data, meta: { take: Number(beforeCtx.take) || take, skip: Number(beforeCtx.skip) || skip, total } };
|
|
283
|
+
|
|
284
|
+
// Execute after middleware
|
|
285
|
+
const afterCtx = await this._executeMiddleware('after', 'getMany', { result });
|
|
286
|
+
return (afterCtx.result as GetManyResult) || result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Internal method to fetch a single record by primary key
|
|
291
|
+
* Performs parallel permission check to distinguish 404 vs 403 errors
|
|
292
|
+
*/
|
|
293
|
+
_get = async (id: string | number | Record<string, any>, include: string | Record<string, any> = '', options: Record<string, any> = {}, fields: string | null = null): Promise<any> => {
|
|
294
|
+
// Execute before middleware
|
|
295
|
+
const beforeCtx = await this._executeMiddleware('before', 'get', { id, include, options, fields });
|
|
296
|
+
|
|
297
|
+
if (beforeCtx.abort) {
|
|
298
|
+
return beforeCtx.result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { omit, ..._options } = beforeCtx.options || options;
|
|
302
|
+
const targetId = beforeCtx.id || id;
|
|
303
|
+
const whereId = this.buildWhereId(targetId);
|
|
304
|
+
|
|
305
|
+
// Build field selection (handles select vs include+omit based on fields param)
|
|
306
|
+
// Use beforeCtx.fields directly since it's always initialized from the context params
|
|
307
|
+
const effectiveFields = beforeCtx.fields as string | null;
|
|
308
|
+
const effectiveInclude = beforeCtx.include || include;
|
|
309
|
+
|
|
310
|
+
let dataQuery: Record<string, any>;
|
|
311
|
+
if (effectiveFields) {
|
|
312
|
+
const fieldSelection = this.queryBuilder.buildFieldSelection(effectiveFields, effectiveInclude, this.user);
|
|
313
|
+
dataQuery = { 'where': whereId, ...fieldSelection, ..._options };
|
|
314
|
+
} else {
|
|
315
|
+
dataQuery = {
|
|
316
|
+
'where': whereId,
|
|
317
|
+
'include': this.include(effectiveInclude),
|
|
318
|
+
'omit': { ...this._omit(), ...omit },
|
|
319
|
+
..._options
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Parallel queries: one for data, one for permission check
|
|
324
|
+
const _response = this.prisma.findUnique(dataQuery);
|
|
325
|
+
|
|
326
|
+
const _checkPermission = this.prisma.findUnique({
|
|
327
|
+
'where': {
|
|
328
|
+
...whereId,
|
|
329
|
+
...this.getAccessFilter()
|
|
330
|
+
},
|
|
331
|
+
'select': this.#buildPrimaryKeySelect()
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const [response, checkPermission] = await Promise.all([_response, _checkPermission]);
|
|
335
|
+
if (response) {
|
|
336
|
+
if (checkPermission) {
|
|
337
|
+
if (!this.#primaryKeysMatch(response, checkPermission)) {
|
|
338
|
+
throw new ErrorResponse(403, "no_permission");
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
throw new ErrorResponse(403, "no_permission");
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
throw new ErrorResponse(404, "record_not_found");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Execute after middleware
|
|
348
|
+
const afterCtx = await this._executeMiddleware('after', 'get', { id: targetId, result: response });
|
|
349
|
+
return afterCtx.result || response;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Internal method to create a new record
|
|
354
|
+
*/
|
|
355
|
+
_create = async (data: Record<string, any>, options: Record<string, any> = {}): Promise<any> => {
|
|
356
|
+
// CHECK CREATE PERMISSION
|
|
357
|
+
if (!this.canCreate(data)) {
|
|
358
|
+
throw new ErrorResponse(403, "no_permission_to_create");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Execute before middleware
|
|
362
|
+
const beforeCtx = await this._executeMiddleware('before', 'create', { data: { ...data }, options });
|
|
363
|
+
|
|
364
|
+
if (beforeCtx.abort) {
|
|
365
|
+
return beforeCtx.result;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const createData = beforeCtx.data || data;
|
|
369
|
+
|
|
370
|
+
// VALIDATE PASSED FIELDS AND RELATIONSHIPS (returns new transformed object)
|
|
371
|
+
const transformedData = this._queryCreate(createData);
|
|
372
|
+
|
|
373
|
+
// CREATE
|
|
374
|
+
const result = await this.prisma.create({
|
|
375
|
+
'data': transformedData,
|
|
376
|
+
'include': this.include('ALL'),
|
|
377
|
+
...(beforeCtx.options || options)
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Execute after middleware
|
|
381
|
+
const afterCtx = await this._executeMiddleware('after', 'create', { data: transformedData, result });
|
|
382
|
+
return afterCtx.result || result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Internal method to update an existing record
|
|
387
|
+
* Automatically removes createdAt/createdBy from update data
|
|
388
|
+
*/
|
|
389
|
+
_update = async (id: string | number | Record<string, any>, data: Record<string, any>, options: Record<string, any> = {}): Promise<any> => {
|
|
390
|
+
// Create a copy to avoid mutating the caller's data
|
|
391
|
+
const inputData: Record<string, any> = { ...data };
|
|
392
|
+
delete inputData.createdAt;
|
|
393
|
+
delete inputData.createdBy;
|
|
394
|
+
|
|
395
|
+
// CHECK UPDATE PERMISSION
|
|
396
|
+
const updateFilter = this.getUpdateFilter();
|
|
397
|
+
if (updateFilter === false) {
|
|
398
|
+
throw new ErrorResponse(403, "no_permission_to_update");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Execute before middleware
|
|
402
|
+
const beforeCtx = await this._executeMiddleware('before', 'update', { id, data: { ...inputData }, options });
|
|
403
|
+
|
|
404
|
+
if (beforeCtx.abort) {
|
|
405
|
+
return beforeCtx.result;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const targetId = beforeCtx.id || id;
|
|
409
|
+
const updateData = beforeCtx.data || inputData;
|
|
410
|
+
|
|
411
|
+
// VALIDATE PASSED FIELDS AND RELATIONSHIPS (returns new transformed object)
|
|
412
|
+
const transformedData = this._queryUpdate(targetId as string | number, updateData);
|
|
413
|
+
|
|
414
|
+
const result = await this.prisma.update({
|
|
415
|
+
'where': {
|
|
416
|
+
...this.buildWhereId(targetId),
|
|
417
|
+
...updateFilter
|
|
418
|
+
},
|
|
419
|
+
'data': transformedData,
|
|
420
|
+
'include': this.include('ALL'),
|
|
421
|
+
...(beforeCtx.options || options)
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
if (!result) {
|
|
425
|
+
throw new ErrorResponse(403, "no_permission");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Execute after middleware
|
|
429
|
+
const afterCtx = await this._executeMiddleware('after', 'update', { id: targetId, data: transformedData, result });
|
|
430
|
+
return afterCtx.result || result;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Internal method to create or update a record based on unique key
|
|
435
|
+
* Supports both single and composite primary keys
|
|
436
|
+
*/
|
|
437
|
+
async _upsert(data: Record<string, any>, unique_key: string | string[] = this.primaryKey, options: Record<string, any> = {}): Promise<any> {
|
|
438
|
+
// Execute before middleware
|
|
439
|
+
const beforeCtx = await this._executeMiddleware('before', 'upsert', { data: { ...data }, unique_key, options });
|
|
440
|
+
|
|
441
|
+
if (beforeCtx.abort) {
|
|
442
|
+
return beforeCtx.result;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const upsertData = beforeCtx.data || data;
|
|
446
|
+
const targetKey = beforeCtx.unique_key || unique_key;
|
|
447
|
+
|
|
448
|
+
// create() and update() are now pure - they return new objects without mutating input
|
|
449
|
+
const createData = this.queryBuilder.create(upsertData, this.user);
|
|
450
|
+
|
|
451
|
+
const updatePrimaryKey = Array.isArray(targetKey) ? targetKey[0] : this.primaryKey;
|
|
452
|
+
const updateData = this.queryBuilder.update(updatePrimaryKey, upsertData, this.user);
|
|
453
|
+
|
|
454
|
+
// Build where clause that supports composite keys
|
|
455
|
+
const whereClause = this.buildWhereUniqueKey(targetKey, upsertData);
|
|
456
|
+
|
|
457
|
+
const result = await this.prisma.upsert({
|
|
458
|
+
'where': whereClause,
|
|
459
|
+
'create': createData,
|
|
460
|
+
'update': updateData,
|
|
461
|
+
'include': this.include('ALL'),
|
|
462
|
+
...(beforeCtx.options || options)
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Execute after middleware
|
|
466
|
+
const afterCtx = await this._executeMiddleware('after', 'upsert', { data: upsertData, result });
|
|
467
|
+
return afterCtx.result || result;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Internal method to create or update multiple records based on unique key
|
|
472
|
+
* Supports both transactional and non-transactional operations with optional relation validation
|
|
473
|
+
*/
|
|
474
|
+
async _upsertMany(
|
|
475
|
+
data: Record<string, any>[],
|
|
476
|
+
unique_key: string | string[] = this.primaryKey,
|
|
477
|
+
prismaOptions: Record<string, any> = {},
|
|
478
|
+
options: UpsertManyOptions = {}
|
|
479
|
+
): Promise<UpsertManyResult> {
|
|
480
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
481
|
+
return { created: 0, updated: 0, failed: [], totalSuccess: 0, totalFailed: 0 } as UpsertManyResult;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Extract operation-specific options
|
|
485
|
+
const validateRelation = options.validateRelation ?? false;
|
|
486
|
+
const useTransaction = options.transaction ?? true;
|
|
487
|
+
const timeout = options.timeout ?? 30000;
|
|
488
|
+
|
|
489
|
+
// Execute before middleware
|
|
490
|
+
const beforeCtx = await this._executeMiddleware('before', 'upsertMany', { data, unique_key, prismaOptions });
|
|
491
|
+
|
|
492
|
+
if (beforeCtx.abort) {
|
|
493
|
+
return beforeCtx.result as UpsertManyResult;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const upsertData: Record<string, any>[] = (Array.isArray(beforeCtx.data) ? beforeCtx.data : data) as Record<string, any>[];
|
|
497
|
+
const targetKey: string | string[] = beforeCtx.unique_key || unique_key;
|
|
498
|
+
const _prismaOptions: Record<string, any> = beforeCtx.prismaOptions || prismaOptions;
|
|
499
|
+
|
|
500
|
+
// Define the upsert operation logic
|
|
501
|
+
const executeUpsertMany = async (tx: any): Promise<UpsertManyResult> => {
|
|
502
|
+
// Find existing records - handle both simple and composite keys
|
|
503
|
+
let existingRecords: Record<string, any>[];
|
|
504
|
+
if (Array.isArray(targetKey)) {
|
|
505
|
+
// Composite key: use OR clause for lookup
|
|
506
|
+
const whereConditions = upsertData
|
|
507
|
+
.map((record: Record<string, any>) => {
|
|
508
|
+
const compositeKeyName = targetKey.join('_');
|
|
509
|
+
const values: Record<string, any> = {};
|
|
510
|
+
targetKey.forEach((k: string) => { values[k] = record[k]; });
|
|
511
|
+
if (Object.values(values).some((v: any) => v == null)) return null;
|
|
512
|
+
return { [compositeKeyName]: values };
|
|
513
|
+
})
|
|
514
|
+
.filter(Boolean) as Record<string, any>[];
|
|
515
|
+
|
|
516
|
+
existingRecords = whereConditions.length > 0
|
|
517
|
+
? await tx[this.name].findMany({
|
|
518
|
+
'where': { OR: whereConditions },
|
|
519
|
+
'select': targetKey.reduce((acc: Record<string, true>, k: string) => { acc[k] = true; return acc; }, {})
|
|
520
|
+
})
|
|
521
|
+
: [];
|
|
522
|
+
} else {
|
|
523
|
+
const uniqueValues = upsertData.map((record: Record<string, any>) => record[targetKey as string]).filter((v: any) => v != null);
|
|
524
|
+
existingRecords = uniqueValues.length > 0
|
|
525
|
+
? await tx[this.name].findMany({
|
|
526
|
+
'where': { [targetKey as string]: { 'in': uniqueValues } },
|
|
527
|
+
'select': { [targetKey as string]: true }
|
|
528
|
+
})
|
|
529
|
+
: [];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Build existence check helper
|
|
533
|
+
const existsInDb = (record: Record<string, any>): boolean => {
|
|
534
|
+
if (Array.isArray(targetKey)) {
|
|
535
|
+
return existingRecords.some((existing: Record<string, any>) =>
|
|
536
|
+
targetKey.every((k: string) => String(existing[k]) === String(record[k]))
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return existingRecords.some((e: Record<string, any>) => String(e[targetKey as string]) === String(record[targetKey as string]));
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// Separate data into creates and updates, using pure create/update
|
|
543
|
+
const createRecords: { original: Record<string, any>; transformed: Record<string, any> }[] = [];
|
|
544
|
+
const updateRecords: { original: Record<string, any>; transformed: Record<string, any> }[] = [];
|
|
545
|
+
|
|
546
|
+
for (const record of upsertData) {
|
|
547
|
+
if (existsInDb(record)) {
|
|
548
|
+
// Record exists, prepare for update (pure - returns new object)
|
|
549
|
+
const updatePrimaryKey = Array.isArray(targetKey) ? targetKey[0] : this.primaryKey;
|
|
550
|
+
const transformedRecord = this.queryBuilder.update(record[updatePrimaryKey] || record[targetKey as string], record, this.user);
|
|
551
|
+
updateRecords.push({ original: record, transformed: transformedRecord });
|
|
552
|
+
} else {
|
|
553
|
+
// Record doesn't exist, prepare for create
|
|
554
|
+
if (validateRelation) {
|
|
555
|
+
const transformedRecord = this.queryBuilder.create(record, this.user);
|
|
556
|
+
createRecords.push({ original: record, transformed: transformedRecord });
|
|
557
|
+
} else {
|
|
558
|
+
createRecords.push({ original: record, transformed: { ...record } });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let createdCount = 0;
|
|
564
|
+
let updatedCount = 0;
|
|
565
|
+
const failed: { record?: Record<string, any>; records?: Record<string, any>[]; error: any }[] = [];
|
|
566
|
+
|
|
567
|
+
// Batch create
|
|
568
|
+
if (createRecords.length > 0) {
|
|
569
|
+
if (validateRelation) {
|
|
570
|
+
for (const { transformed } of createRecords) {
|
|
571
|
+
try {
|
|
572
|
+
await tx[this.name].create({
|
|
573
|
+
'data': transformed,
|
|
574
|
+
..._prismaOptions
|
|
575
|
+
});
|
|
576
|
+
createdCount++;
|
|
577
|
+
} catch (error: any) {
|
|
578
|
+
failed.push({ record: transformed, error });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
try {
|
|
583
|
+
const createResult = await tx[this.name].createMany({
|
|
584
|
+
'data': createRecords.map((r: { original: Record<string, any>; transformed: Record<string, any> }) => r.transformed),
|
|
585
|
+
'skipDuplicates': true,
|
|
586
|
+
..._prismaOptions
|
|
587
|
+
});
|
|
588
|
+
createdCount = createResult.count;
|
|
589
|
+
} catch (error: any) {
|
|
590
|
+
failed.push({ records: createRecords.map((r: { original: Record<string, any>; transformed: Record<string, any> }) => r.transformed), error });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Batch update
|
|
595
|
+
if (updateRecords.length > 0) {
|
|
596
|
+
for (const { original, transformed } of updateRecords) {
|
|
597
|
+
try {
|
|
598
|
+
const whereClause = Array.isArray(targetKey)
|
|
599
|
+
? this.buildWhereUniqueKey(targetKey, original)
|
|
600
|
+
: { [targetKey as string]: original[targetKey as string] };
|
|
601
|
+
await tx[this.name].update({
|
|
602
|
+
'where': whereClause,
|
|
603
|
+
'data': transformed,
|
|
604
|
+
..._prismaOptions
|
|
605
|
+
});
|
|
606
|
+
updatedCount++;
|
|
607
|
+
} catch (error: any) {
|
|
608
|
+
failed.push({ record: transformed, error });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
created: createdCount,
|
|
615
|
+
updated: updatedCount,
|
|
616
|
+
failed,
|
|
617
|
+
totalSuccess: createdCount + updatedCount,
|
|
618
|
+
totalFailed: failed.length
|
|
619
|
+
} as UpsertManyResult;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// Execute with or without transaction based on option
|
|
623
|
+
const result: UpsertManyResult = useTransaction
|
|
624
|
+
? await prismaTransaction(executeUpsertMany, { timeout })
|
|
625
|
+
: await executeUpsertMany(prisma);
|
|
626
|
+
|
|
627
|
+
// Execute after middleware
|
|
628
|
+
const afterCtx = await this._executeMiddleware('after', 'upsertMany', { data: upsertData, result });
|
|
629
|
+
return (afterCtx.result as UpsertManyResult) || result;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Internal method to count records matching a filter
|
|
634
|
+
*/
|
|
635
|
+
_count = async (q: Record<string, any> = {}): Promise<number> => {
|
|
636
|
+
// Execute before middleware
|
|
637
|
+
const beforeCtx = await this._executeMiddleware('before', 'count', { query: q });
|
|
638
|
+
|
|
639
|
+
if (beforeCtx.abort) {
|
|
640
|
+
return (beforeCtx.result as number) || 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const result = await this.prisma.count({
|
|
644
|
+
'where': this.filter(beforeCtx.query || q)
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Execute after middleware
|
|
648
|
+
const afterCtx = await this._executeMiddleware('after', 'count', { query: beforeCtx.query || q, result });
|
|
649
|
+
return (afterCtx.result as number) ?? result;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Internal method to delete a record by primary key
|
|
654
|
+
*/
|
|
655
|
+
_delete = async (id: string | number | Record<string, any>, options: Record<string, any> = {}): Promise<any> => {
|
|
656
|
+
// CHECK DELETE PERMISSION
|
|
657
|
+
const deleteFilter = this.getDeleteFilter();
|
|
658
|
+
if (deleteFilter === false) {
|
|
659
|
+
throw new ErrorResponse(403, "no_permission_to_delete");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Execute before middleware
|
|
663
|
+
const beforeCtx = await this._executeMiddleware('before', 'delete', { id, options });
|
|
664
|
+
|
|
665
|
+
if (beforeCtx.abort) {
|
|
666
|
+
return beforeCtx.result;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const targetId = beforeCtx.id || id;
|
|
670
|
+
const whereId = this.buildWhereId(targetId);
|
|
671
|
+
|
|
672
|
+
// Support soft delete via middleware
|
|
673
|
+
if (beforeCtx.softDelete && beforeCtx.data) {
|
|
674
|
+
const result = await this.prisma.update({
|
|
675
|
+
'where': {
|
|
676
|
+
...whereId,
|
|
677
|
+
...deleteFilter
|
|
678
|
+
},
|
|
679
|
+
'data': beforeCtx.data,
|
|
680
|
+
'select': this.select(),
|
|
681
|
+
...(beforeCtx.options || options)
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const afterCtx = await this._executeMiddleware('after', 'delete', { id: targetId, result, softDelete: true });
|
|
685
|
+
return afterCtx.result || result;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const result = await this.prisma.delete({
|
|
689
|
+
'where': {
|
|
690
|
+
...whereId,
|
|
691
|
+
...deleteFilter
|
|
692
|
+
},
|
|
693
|
+
'select': this.select(),
|
|
694
|
+
...(beforeCtx.options || options)
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
if (!result) {
|
|
698
|
+
throw new ErrorResponse(403, "no_permission");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Execute after middleware
|
|
702
|
+
const afterCtx = await this._executeMiddleware('after', 'delete', { id: targetId, result });
|
|
703
|
+
return afterCtx.result || result;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Fetch multiple records with filtering, pagination, and sorting
|
|
708
|
+
*/
|
|
709
|
+
async getMany(
|
|
710
|
+
q: Record<string, any> = {},
|
|
711
|
+
include: string | Record<string, any> = "",
|
|
712
|
+
limit: number = 25,
|
|
713
|
+
offset: number = 0,
|
|
714
|
+
sortBy: string = this.defaultSortField,
|
|
715
|
+
sortOrder: string = "asc",
|
|
716
|
+
fields: string | null = null
|
|
717
|
+
): Promise<GetManyResult> {
|
|
718
|
+
return await this._getMany(q, include, Number(limit), Number(offset), sortBy, sortOrder, {}, fields);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Fetch a single record by primary key
|
|
723
|
+
*/
|
|
724
|
+
async get(id: string | number | Record<string, any>, include?: string | Record<string, any>, options: Record<string, any> = {}, fields: string | null = null): Promise<any> {
|
|
725
|
+
return await this._get(id, include, options, fields);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Create a new record
|
|
730
|
+
*/
|
|
731
|
+
async create(data: Record<string, any>, options: Record<string, any> = {}): Promise<any> {
|
|
732
|
+
return await this._create(data, options);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Update an existing record by primary key
|
|
737
|
+
*/
|
|
738
|
+
async update(id: string | number | Record<string, any>, data: Record<string, any>, options: Record<string, any> = {}): Promise<any> {
|
|
739
|
+
return await this._update(id, data, options);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Create or update a record based on unique key
|
|
744
|
+
*/
|
|
745
|
+
async upsert(data: Record<string, any>, unique_key: string | string[] = this.primaryKey, options: Record<string, any> = {}): Promise<any> {
|
|
746
|
+
return await this._upsert(data, unique_key, options);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Create or update multiple records based on unique key
|
|
751
|
+
* Performs atomic batch operations with optional transaction support
|
|
752
|
+
*/
|
|
753
|
+
async upsertMany(
|
|
754
|
+
data: Record<string, any>[],
|
|
755
|
+
unique_key: string | string[] = this.primaryKey,
|
|
756
|
+
prismaOptions: Record<string, any> = {},
|
|
757
|
+
options: UpsertManyOptions = {}
|
|
758
|
+
): Promise<UpsertManyResult> {
|
|
759
|
+
return await this._upsertMany(data, unique_key, prismaOptions, options);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Count records matching a filter
|
|
764
|
+
*/
|
|
765
|
+
async count(q: Record<string, any> = {}): Promise<number> {
|
|
766
|
+
return await this._count(q);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Delete a record by primary key
|
|
771
|
+
*/
|
|
772
|
+
async delete(id: string | number | Record<string, any>, options: Record<string, any> = {}): Promise<any> {
|
|
773
|
+
return await this._delete(id, options);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Build a select clause for queries
|
|
778
|
+
*/
|
|
779
|
+
select(fields?: string[] | Record<string, any>): Record<string, any> {
|
|
780
|
+
return this._select(fields);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Build a filter/where clause with ACL applied
|
|
785
|
+
*/
|
|
786
|
+
filter(include: string | Record<string, any>): Record<string, any> {
|
|
787
|
+
return { ...this._filter(include), ...this.getAccessFilter() };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Build an include clause for relations
|
|
792
|
+
*/
|
|
793
|
+
include(include: string | Record<string, any>): Record<string, any> {
|
|
794
|
+
return this._include(include);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Build an orderBy clause
|
|
799
|
+
*/
|
|
800
|
+
sort(sortBy: string, sortOrder: string): Record<string, any> {
|
|
801
|
+
return this.queryBuilder.sort(sortBy, sortOrder);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Normalize and validate the limit (take) value
|
|
806
|
+
*/
|
|
807
|
+
take(limit: number): number {
|
|
808
|
+
return this.queryBuilder.take(Number(limit));
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Normalize and validate the offset (skip) value
|
|
813
|
+
*/
|
|
814
|
+
skip(offset: number | string): number {
|
|
815
|
+
const parsed = parseInt(offset as string);
|
|
816
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
817
|
+
return 0;
|
|
818
|
+
}
|
|
819
|
+
return parsed;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Get access filter for read operations
|
|
824
|
+
* Returns empty filter for 'application' role or when ACL returns true
|
|
825
|
+
*/
|
|
826
|
+
getAccessFilter(): Record<string, any> {
|
|
827
|
+
const filter = this._getAccessFilter();
|
|
828
|
+
if (this.user.role == "application" || filter === true) {
|
|
829
|
+
return {};
|
|
830
|
+
}
|
|
831
|
+
if (filter === false) {
|
|
832
|
+
throw new ErrorResponse(403, "no_permission");
|
|
833
|
+
}
|
|
834
|
+
if (!filter || typeof filter !== 'object') {
|
|
835
|
+
return {};
|
|
836
|
+
}
|
|
837
|
+
return filter;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Check if user has permission to create records
|
|
842
|
+
*/
|
|
843
|
+
canCreate(data?: Record<string, any>): boolean {
|
|
844
|
+
if (this.user.role == "application") return true;
|
|
845
|
+
return this._canCreate(data);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Get filter for update operations
|
|
850
|
+
* Returns empty filter for 'application' role or when ACL returns true
|
|
851
|
+
*/
|
|
852
|
+
getUpdateFilter(): Record<string, any> | false {
|
|
853
|
+
const filter = this._getUpdateFilter();
|
|
854
|
+
if (this.user.role == "application" || filter === true) {
|
|
855
|
+
return {};
|
|
856
|
+
}
|
|
857
|
+
return filter;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Get filter for delete operations
|
|
862
|
+
* Returns empty filter for 'application' role or when ACL returns true
|
|
863
|
+
*/
|
|
864
|
+
getDeleteFilter(): Record<string, any> | false {
|
|
865
|
+
const filter = this._getDeleteFilter();
|
|
866
|
+
if (this.user.role == "application" || filter === true) {
|
|
867
|
+
return {};
|
|
868
|
+
}
|
|
869
|
+
return filter;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Set the model name and initialize the Prisma client delegate
|
|
874
|
+
*/
|
|
875
|
+
set modelName(name: string) {
|
|
876
|
+
this.name = name;
|
|
877
|
+
this.prisma = (prisma as any)[name];
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export { Model, QueryBuilder, prisma };
|