adminizer 4.1.0-build.19 → 4.1.0-build.20
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.
|
@@ -131,8 +131,8 @@ export class NodeTable {
|
|
|
131
131
|
async output(callback, dataAccessor) {
|
|
132
132
|
try {
|
|
133
133
|
const queryOptions = await this.buildQuery();
|
|
134
|
-
const totalRecords = await this.model.count({});
|
|
135
|
-
const filteredRecords = await this.model.count(queryOptions.where);
|
|
134
|
+
const totalRecords = await this.model.count({}, dataAccessor);
|
|
135
|
+
const filteredRecords = await this.model.count(queryOptions.where, dataAccessor);
|
|
136
136
|
const data = await this.model.find(queryOptions, dataAccessor);
|
|
137
137
|
const output = {
|
|
138
138
|
draw: this.request.draw !== "" ? this.request.draw : 0,
|
package/lib/v4/DataAccessor.js
CHANGED
|
@@ -7,7 +7,6 @@ export class DataAccessor {
|
|
|
7
7
|
action;
|
|
8
8
|
fields = null;
|
|
9
9
|
actionVerb;
|
|
10
|
-
// TODO: change req to adminizr + user
|
|
11
10
|
constructor(adminizer, user, entity, action) {
|
|
12
11
|
this.adminizer = adminizer;
|
|
13
12
|
this.user = user;
|
|
@@ -38,6 +37,10 @@ export class DataAccessor {
|
|
|
38
37
|
}
|
|
39
38
|
const result = {};
|
|
40
39
|
Object.entries(modelAttributes).forEach(([key, modelField]) => {
|
|
40
|
+
// The fields that are recorded separately from the connection in some ORMs, because they are processed at the level above them.
|
|
41
|
+
if (modelAttributes[key].primaryKeyForAssociation === true) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
41
44
|
// Checks for short type in Waterline: fieldName: 'string'
|
|
42
45
|
if (typeof modelField === "string") {
|
|
43
46
|
modelField = { type: modelField };
|
|
@@ -74,7 +77,6 @@ export class DataAccessor {
|
|
|
74
77
|
let populatedModelFieldsConfig = {};
|
|
75
78
|
if (modelField.type === "association" || modelField.type === "association-many") {
|
|
76
79
|
const modelName = modelField.model || modelField.collection;
|
|
77
|
-
// Todo: test > hide relation without model token
|
|
78
80
|
const tokenId = `${this.actionVerb}-${modelName}-${this.entity.type}`;
|
|
79
81
|
if (!this.adminizer.accessRightsHelper.hasPermission(tokenId, this.user)) {
|
|
80
82
|
Adminizer.log.silly(`No access rights to ${this.entity.type}: ${this.entity.model.modelname}`);
|
|
@@ -340,7 +342,7 @@ export class DataAccessor {
|
|
|
340
342
|
throw new Error(`No intermediate record found in model "${intermediateRelation.model}" associated with user ID "${this.user.id}"`);
|
|
341
343
|
}
|
|
342
344
|
// Ensure there is only one associated intermediate record
|
|
343
|
-
const intermediateRecordCount = await intermediateModel.count({ [via]: this.user.id });
|
|
345
|
+
const intermediateRecordCount = await intermediateModel.count({ [via]: this.user.id }, this);
|
|
344
346
|
if (intermediateRecordCount > 1) {
|
|
345
347
|
throw new Error(`Multiple intermediate records found in model "${intermediateRelation.model}" associated with user ID "${this.user.id}". ` +
|
|
346
348
|
`Expected only one`);
|
|
@@ -423,7 +425,7 @@ export class DataAccessor {
|
|
|
423
425
|
throw new Error(`No intermediate record found in model "${intermediateRelation.model}" linking user "${this.user.id}" to the main record`);
|
|
424
426
|
}
|
|
425
427
|
// Ensure there is only one associated intermediate record
|
|
426
|
-
const intermediateRecordCount = await intermediateModel.count({ [via]: this.user.id });
|
|
428
|
+
const intermediateRecordCount = await intermediateModel.count({ [via]: this.user.id }, this);
|
|
427
429
|
if (intermediateRecordCount > 1) {
|
|
428
430
|
throw new Error(`Multiple intermediate records found in model "${intermediateRelation.model}" associated with user ID "${this.user.id}". ` +
|
|
429
431
|
`Expected only one`);
|
|
@@ -432,7 +434,6 @@ export class DataAccessor {
|
|
|
432
434
|
updatedRecord[field] = intermediateRecord.id;
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
|
-
console.log(updatedRecord, "<<<<<<<<<<<<<<<<<<<,");
|
|
436
437
|
return updatedRecord;
|
|
437
438
|
}
|
|
438
439
|
}
|
|
@@ -10,6 +10,10 @@ export interface Attribute {
|
|
|
10
10
|
collection?: string;
|
|
11
11
|
via?: string;
|
|
12
12
|
allowNull?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* This is a field that indicates that it refers to keep the bond key 1: 1
|
|
15
|
+
*/
|
|
16
|
+
primaryKeyForAssociation?: boolean;
|
|
13
17
|
}
|
|
14
18
|
export interface ModelAttributes {
|
|
15
19
|
[key: string]: Attribute;
|
|
@@ -49,7 +53,7 @@ export declare abstract class AbstractModel<T> {
|
|
|
49
53
|
update(criteria: Partial<T>, data: Partial<T>, dataAccessor: DataAccessor): Promise<Partial<T>[]>;
|
|
50
54
|
destroyOne(criteria: Partial<T>, dataAccessor: DataAccessor): Promise<Partial<T> | null>;
|
|
51
55
|
destroy(criteria: Partial<T>, dataAccessor: DataAccessor): Promise<Partial<T>[]>;
|
|
52
|
-
count(criteria: Partial<T> | undefined): Promise<number>;
|
|
56
|
+
count(criteria: Partial<T> | undefined, dataAccessor: DataAccessor): Promise<number>;
|
|
53
57
|
}
|
|
54
58
|
export declare abstract class AbstractAdapter {
|
|
55
59
|
abstract Model: any;
|
|
@@ -46,7 +46,8 @@ export class AbstractModel {
|
|
|
46
46
|
let records = await this._destroy(criteria);
|
|
47
47
|
return records.map(record => dataAccessor.process(record));
|
|
48
48
|
}
|
|
49
|
-
async count(criteria) {
|
|
49
|
+
async count(criteria, dataAccessor) {
|
|
50
|
+
criteria = await dataAccessor.sanitizeUserRelationAccess(criteria);
|
|
50
51
|
return this._count(criteria);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -4,6 +4,13 @@ export declare function mapSequelizeToWaterline(model: ModelStatic<any>): Record
|
|
|
4
4
|
export declare class SequelizeModel<T> extends AbstractModel<T> {
|
|
5
5
|
private model;
|
|
6
6
|
constructor(modelName: string, model: ModelStatic<any>);
|
|
7
|
+
_convertCriteriaToSequelize(criteria: any): any;
|
|
8
|
+
_convertWaterlineCriteriaToSequelizeOptions(criteria: any): {
|
|
9
|
+
where?: any;
|
|
10
|
+
limit?: number;
|
|
11
|
+
offset?: number;
|
|
12
|
+
order?: any[];
|
|
13
|
+
};
|
|
7
14
|
_assignAssociations(instance: any, assocData: Record<string, any>): Promise<void>;
|
|
8
15
|
protected _create(data: Record<string, any>): Promise<T>;
|
|
9
16
|
protected _findOne(criteria: Partial<T>): Promise<T | null>;
|
|
@@ -96,6 +96,7 @@ export function mapSequelizeToWaterline(model) {
|
|
|
96
96
|
switch (assoc.associationType) {
|
|
97
97
|
case "BelongsTo": {
|
|
98
98
|
const a = assoc;
|
|
99
|
+
result[a.foreignKey]["primaryKeyForAssociation"] = true;
|
|
99
100
|
result[alias] = {
|
|
100
101
|
type: "association",
|
|
101
102
|
model: a.target.name.toLowerCase(),
|
|
@@ -105,6 +106,7 @@ export function mapSequelizeToWaterline(model) {
|
|
|
105
106
|
}
|
|
106
107
|
case "HasOne": {
|
|
107
108
|
const a = assoc;
|
|
109
|
+
result[a.foreignKey]["primaryKeyForAssociation"] = true;
|
|
108
110
|
result[alias] = {
|
|
109
111
|
type: "association",
|
|
110
112
|
model: a.target.name.toLowerCase(),
|
|
@@ -139,93 +141,79 @@ export function mapSequelizeToWaterline(model) {
|
|
|
139
141
|
function capitalize(str) {
|
|
140
142
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
141
143
|
}
|
|
142
|
-
function convertCriteriaToSequelize(criteria) {
|
|
143
|
-
// console.debug("waterline criteria", criteria)
|
|
144
|
-
const result = {};
|
|
145
|
-
for (const key in criteria) {
|
|
146
|
-
const value = criteria[key];
|
|
147
|
-
if (value === undefined ||
|
|
148
|
-
value === null ||
|
|
149
|
-
(typeof value === "object" && Object.keys(value).length === 0)) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
153
|
-
const operatorEntries = Object.entries(value)
|
|
154
|
-
.filter(([_, v]) => v !== undefined && v !== null)
|
|
155
|
-
.map(([op, val]) => {
|
|
156
|
-
switch (op) {
|
|
157
|
-
case "contains": return [Op.like, `%${val}%`];
|
|
158
|
-
case "startsWith": return [Op.startsWith, val];
|
|
159
|
-
case "endsWith": return [Op.endsWith, val];
|
|
160
|
-
case ">": return [Op.gt, val];
|
|
161
|
-
case ">=": return [Op.gte, val];
|
|
162
|
-
case "<": return [Op.lt, val];
|
|
163
|
-
case "<=": return [Op.lte, val];
|
|
164
|
-
case "!=": return [Op.ne, val];
|
|
165
|
-
case "in": return [Op.in, val];
|
|
166
|
-
case "nin": return [Op.notIn, val];
|
|
167
|
-
default: return [Op.eq, val];
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
if (operatorEntries.length > 0) {
|
|
171
|
-
result[key] = Object.fromEntries(operatorEntries);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
result[key] = value;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return result;
|
|
179
|
-
}
|
|
180
|
-
function convertWaterlineCriteriaToSequelizeOptions(criteria) {
|
|
181
|
-
// console.debug("WATERLINE CRITERIA (raw):", criteria);
|
|
182
|
-
const { where: nestedWhere, skip, limit, sort, ...rest } = criteria;
|
|
183
|
-
const rawWhere = (nestedWhere && Object.keys(nestedWhere).length > 0)
|
|
184
|
-
? nestedWhere
|
|
185
|
-
: rest;
|
|
186
|
-
// console.debug("WATERLINE CRITERIA: using rawWhere =", rawWhere);
|
|
187
|
-
const where = convertCriteriaToSequelize(rawWhere);
|
|
188
|
-
// console.debug("convertCriteriaToSequelize →", where);
|
|
189
|
-
const result = { where };
|
|
190
|
-
if (typeof skip === "number") {
|
|
191
|
-
result.offset = skip;
|
|
192
|
-
// console.debug("→ offset =", skip);
|
|
193
|
-
}
|
|
194
|
-
if (typeof limit === "number") {
|
|
195
|
-
result.limit = limit;
|
|
196
|
-
// console.debug("→ limit =", limit);
|
|
197
|
-
}
|
|
198
|
-
if (typeof sort === "string") {
|
|
199
|
-
const [field, dir] = sort.trim().split(/\s+/);
|
|
200
|
-
result.order = [[field, dir?.toUpperCase() === "DESC" ? "DESC" : "ASC"]];
|
|
201
|
-
// console.debug("→ order =", result.order);
|
|
202
|
-
}
|
|
203
|
-
return result;
|
|
204
|
-
}
|
|
205
|
-
function convertWaterlineCriteriaToSequelizeOptions____OLD(criteria) {
|
|
206
|
-
// console.debug("WATERLINE CRITERIA", criteria)
|
|
207
|
-
const { where = {}, skip, limit, sort } = criteria;
|
|
208
|
-
const result = {
|
|
209
|
-
where: convertCriteriaToSequelize(where),
|
|
210
|
-
};
|
|
211
|
-
if (typeof skip === "number") {
|
|
212
|
-
result.offset = skip;
|
|
213
|
-
}
|
|
214
|
-
if (typeof limit === "number") {
|
|
215
|
-
result.limit = limit;
|
|
216
|
-
}
|
|
217
|
-
if (typeof sort === "string") {
|
|
218
|
-
const [field, direction] = sort.trim().split(/\s+/);
|
|
219
|
-
result.order = [[field, direction?.toUpperCase() === "DESC" ? "DESC" : "ASC"]];
|
|
220
|
-
}
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
144
|
export class SequelizeModel extends AbstractModel {
|
|
224
145
|
model;
|
|
225
146
|
constructor(modelName, model) {
|
|
226
147
|
super(modelName, mapSequelizeToWaterline(model), model.primaryKeyAttribute, model.name);
|
|
227
148
|
this.model = model;
|
|
228
149
|
}
|
|
150
|
+
_convertCriteriaToSequelize(criteria) {
|
|
151
|
+
const result = {};
|
|
152
|
+
for (const key in criteria) {
|
|
153
|
+
const value = criteria[key];
|
|
154
|
+
if (value === undefined ||
|
|
155
|
+
value === null ||
|
|
156
|
+
(typeof value === "object" && Object.keys(value).length === 0)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// 🧠 Заменяем ключ на `via`, если это ассоциация
|
|
160
|
+
const attr = this.attributes?.[key];
|
|
161
|
+
let targetKey = key;
|
|
162
|
+
if (attr?.type === "association" && attr.via) {
|
|
163
|
+
targetKey = attr.via;
|
|
164
|
+
}
|
|
165
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
166
|
+
const operatorEntries = Object.entries(value)
|
|
167
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
168
|
+
.map(([op, val]) => {
|
|
169
|
+
switch (op) {
|
|
170
|
+
case "contains": return [Op.like, `%${val}%`];
|
|
171
|
+
case "startsWith": return [Op.startsWith, val];
|
|
172
|
+
case "endsWith": return [Op.endsWith, val];
|
|
173
|
+
case ">": return [Op.gt, val];
|
|
174
|
+
case ">=": return [Op.gte, val];
|
|
175
|
+
case "<": return [Op.lt, val];
|
|
176
|
+
case "<=": return [Op.lte, val];
|
|
177
|
+
case "!=": return [Op.ne, val];
|
|
178
|
+
case "in": return [Op.in, val];
|
|
179
|
+
case "nin": return [Op.notIn, val];
|
|
180
|
+
default: return [Op.eq, val];
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
if (operatorEntries.length > 0) {
|
|
184
|
+
result[targetKey] = Object.fromEntries(operatorEntries);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
result[targetKey] = value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
_convertWaterlineCriteriaToSequelizeOptions(criteria) {
|
|
194
|
+
// console.debug("WATERLINE CRITERIA (raw):", criteria);
|
|
195
|
+
const { where: nestedWhere, skip, limit, sort, ...rest } = criteria;
|
|
196
|
+
const rawWhere = (nestedWhere && Object.keys(nestedWhere).length > 0)
|
|
197
|
+
? nestedWhere
|
|
198
|
+
: rest;
|
|
199
|
+
// console.debug("WATERLINE CRITERIA: using rawWhere =", rawWhere);
|
|
200
|
+
const where = this._convertCriteriaToSequelize(rawWhere);
|
|
201
|
+
const result = { where };
|
|
202
|
+
if (typeof skip === "number") {
|
|
203
|
+
result.offset = skip;
|
|
204
|
+
// console.debug("→ offset =", skip);
|
|
205
|
+
}
|
|
206
|
+
if (typeof limit === "number") {
|
|
207
|
+
result.limit = limit;
|
|
208
|
+
// console.debug("→ limit =", limit);
|
|
209
|
+
}
|
|
210
|
+
if (typeof sort === "string") {
|
|
211
|
+
const [field, dir] = sort.trim().split(/\s+/);
|
|
212
|
+
result.order = [[field, dir?.toUpperCase() === "DESC" ? "DESC" : "ASC"]];
|
|
213
|
+
// console.debug("→ order =", result.order);
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
229
217
|
async _assignAssociations(instance, assocData) {
|
|
230
218
|
for (const [alias, ids] of Object.entries(assocData)) {
|
|
231
219
|
const assoc = this.model.associations[alias];
|
|
@@ -321,7 +309,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
321
309
|
// --- FIND ONE ---
|
|
322
310
|
async _findOne(criteria) {
|
|
323
311
|
// console.debug(">> _findOne: входные критерии:", criteria);
|
|
324
|
-
const { where } =
|
|
312
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
325
313
|
const includes = this._buildIncludes();
|
|
326
314
|
// console.debug(">> _findOne: преобразованные where:", where);
|
|
327
315
|
// console.debug(">> _findOne: includes:", includes);
|
|
@@ -346,7 +334,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
346
334
|
async _find(criteria = {}, options = {}) {
|
|
347
335
|
const assocNames = Object.keys(this.model.associations);
|
|
348
336
|
// console.debug(">> _find: входные criteria:", criteria, "options:", options);
|
|
349
|
-
const { where, limit, offset, order } =
|
|
337
|
+
const { where, limit, offset, order } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
350
338
|
const includes = options.populate
|
|
351
339
|
? options.populate.map(([field, opts]) => ({ association: field, ...opts }))
|
|
352
340
|
: assocNames.map(a => ({ association: a }));
|
|
@@ -358,20 +346,13 @@ export class SequelizeModel extends AbstractModel {
|
|
|
358
346
|
// includes,
|
|
359
347
|
// });
|
|
360
348
|
let instances;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
369
|
-
// console.debug(">> _find: получено моделей:", instances.length);
|
|
370
|
-
}
|
|
371
|
-
catch (err) {
|
|
372
|
-
// console.error("!! _find: ошибка в findAll:", err);
|
|
373
|
-
throw err;
|
|
374
|
-
}
|
|
349
|
+
instances = await this.model.findAll({
|
|
350
|
+
where,
|
|
351
|
+
limit,
|
|
352
|
+
offset,
|
|
353
|
+
order,
|
|
354
|
+
include: includes
|
|
355
|
+
});
|
|
375
356
|
for (const inst of instances) {
|
|
376
357
|
//For each association, we call getxxx () once again
|
|
377
358
|
for (const alias of assocNames) {
|
|
@@ -397,7 +378,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
397
378
|
}
|
|
398
379
|
// --- UPDATE ONE ---
|
|
399
380
|
async _updateOne(criteria, data) {
|
|
400
|
-
const { where } =
|
|
381
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
401
382
|
const record = await this.model.findOne({ where });
|
|
402
383
|
if (!record)
|
|
403
384
|
return null;
|
|
@@ -419,7 +400,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
419
400
|
}
|
|
420
401
|
// --- UPDATE MANY ---
|
|
421
402
|
async _update(criteria, data) {
|
|
422
|
-
const { where } =
|
|
403
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
423
404
|
const assocNames = Object.keys(this.model.associations);
|
|
424
405
|
const plainData = {};
|
|
425
406
|
const assocData = {};
|
|
@@ -444,7 +425,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
444
425
|
}
|
|
445
426
|
// --- DESTROY ONE ---
|
|
446
427
|
async _destroyOne(criteria) {
|
|
447
|
-
const { where } =
|
|
428
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
448
429
|
const record = await this.model.findOne({ where });
|
|
449
430
|
if (!record)
|
|
450
431
|
return null;
|
|
@@ -479,7 +460,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
479
460
|
}
|
|
480
461
|
// --- DESTROY MANY ---
|
|
481
462
|
async _destroy(criteria) {
|
|
482
|
-
const { where } =
|
|
463
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
483
464
|
const records = await this.model.findAll({ where });
|
|
484
465
|
const assocNames = Object.keys(this.model.associations);
|
|
485
466
|
for (const record of records) {
|
|
@@ -516,7 +497,7 @@ export class SequelizeModel extends AbstractModel {
|
|
|
516
497
|
}
|
|
517
498
|
// --- COUNT ---
|
|
518
499
|
async _count(criteria = {}) {
|
|
519
|
-
const { where } =
|
|
500
|
+
const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
|
|
520
501
|
const result = await this.model.count({ where });
|
|
521
502
|
return result;
|
|
522
503
|
}
|