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,
@@ -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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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
- try {
362
- instances = await this.model.findAll({
363
- where,
364
- limit,
365
- offset,
366
- order,
367
- include: includes
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
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 } = convertWaterlineCriteriaToSequelizeOptions(criteria);
500
+ const { where } = this._convertWaterlineCriteriaToSequelizeOptions(criteria);
520
501
  const result = await this.model.count({ where });
521
502
  return result;
522
503
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adminizer",
3
3
  "type": "module",
4
- "version": "4.1.0-build.19",
4
+ "version": "4.1.0-build.20",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "vitest --reporter verbose",