@saltcorn/data 0.8.2 → 0.8.3-alpha.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.
Files changed (110) hide show
  1. package/dist/base-plugin/actions.d.ts.map +1 -1
  2. package/dist/base-plugin/actions.js +5 -3
  3. package/dist/base-plugin/actions.js.map +1 -1
  4. package/dist/base-plugin/index.d.ts +249 -256
  5. package/dist/base-plugin/index.d.ts.map +1 -1
  6. package/dist/base-plugin/types.d.ts.map +1 -1
  7. package/dist/base-plugin/types.js +11 -3
  8. package/dist/base-plugin/types.js.map +1 -1
  9. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  10. package/dist/base-plugin/viewtemplates/edit.js +24 -7
  11. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  12. package/dist/base-plugin/viewtemplates/feed.d.ts +1 -1
  13. package/dist/base-plugin/viewtemplates/feed.d.ts.map +1 -1
  14. package/dist/base-plugin/viewtemplates/feed.js +16 -1
  15. package/dist/base-plugin/viewtemplates/feed.js.map +1 -1
  16. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  17. package/dist/base-plugin/viewtemplates/list.js +5 -15
  18. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  19. package/dist/base-plugin/viewtemplates/listshowlist.d.ts.map +1 -1
  20. package/dist/base-plugin/viewtemplates/listshowlist.js +5 -1
  21. package/dist/base-plugin/viewtemplates/listshowlist.js.map +1 -1
  22. package/dist/base-plugin/viewtemplates/room.d.ts +1 -1
  23. package/dist/base-plugin/viewtemplates/room.js +2 -2
  24. package/dist/base-plugin/viewtemplates/room.js.map +1 -1
  25. package/dist/base-plugin/viewtemplates/show.d.ts +0 -7
  26. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  27. package/dist/base-plugin/viewtemplates/show.js +34 -31
  28. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  29. package/dist/db/fixtures.d.ts.map +1 -1
  30. package/dist/db/fixtures.js +167 -0
  31. package/dist/db/fixtures.js.map +1 -1
  32. package/dist/db/state.js +1 -1
  33. package/dist/db/state.js.map +1 -1
  34. package/dist/diagram/cy_generate_utils.d.ts.map +1 -1
  35. package/dist/diagram/cy_generate_utils.js +6 -0
  36. package/dist/diagram/cy_generate_utils.js.map +1 -1
  37. package/dist/diagram/node_extract_utils.js +14 -5
  38. package/dist/diagram/node_extract_utils.js.map +1 -1
  39. package/dist/diagram/nodes/node.d.ts +2 -2
  40. package/dist/diagram/nodes/node.d.ts.map +1 -1
  41. package/dist/diagram/nodes/node.js +2 -2
  42. package/dist/diagram/nodes/node.js.map +1 -1
  43. package/dist/diagram/nodes/trigger_node.d.ts +1 -3
  44. package/dist/diagram/nodes/trigger_node.d.ts.map +1 -1
  45. package/dist/diagram/nodes/trigger_node.js +3 -3
  46. package/dist/diagram/nodes/trigger_node.js.map +1 -1
  47. package/dist/migrations/202301261705.d.ts +3 -0
  48. package/dist/migrations/202301261705.d.ts.map +1 -0
  49. package/dist/migrations/202301261705.js +5 -0
  50. package/dist/migrations/202301261705.js.map +1 -0
  51. package/dist/models/config.d.ts.map +1 -1
  52. package/dist/models/config.js +1 -0
  53. package/dist/models/config.js.map +1 -1
  54. package/dist/models/email.d.ts +2 -0
  55. package/dist/models/email.d.ts.map +1 -1
  56. package/dist/models/eventlog.d.ts.map +1 -1
  57. package/dist/models/eventlog.js +7 -2
  58. package/dist/models/eventlog.js.map +1 -1
  59. package/dist/models/expression.d.ts +2 -1
  60. package/dist/models/expression.d.ts.map +1 -1
  61. package/dist/models/expression.js +42 -0
  62. package/dist/models/expression.js.map +1 -1
  63. package/dist/models/field.d.ts.map +1 -1
  64. package/dist/models/field.js +14 -3
  65. package/dist/models/field.js.map +1 -1
  66. package/dist/models/random.d.ts.map +1 -1
  67. package/dist/models/random.js +5 -4
  68. package/dist/models/random.js.map +1 -1
  69. package/dist/models/table.d.ts +40 -8
  70. package/dist/models/table.d.ts.map +1 -1
  71. package/dist/models/table.js +351 -46
  72. package/dist/models/table.js.map +1 -1
  73. package/dist/models/user.d.ts.map +1 -1
  74. package/dist/models/user.js +5 -0
  75. package/dist/models/user.js.map +1 -1
  76. package/dist/models/workflow.d.ts.map +1 -1
  77. package/dist/models/workflow.js +31 -0
  78. package/dist/models/workflow.js.map +1 -1
  79. package/dist/plugin-helper.d.ts +12 -8
  80. package/dist/plugin-helper.d.ts.map +1 -1
  81. package/dist/plugin-helper.js +45 -47
  82. package/dist/plugin-helper.js.map +1 -1
  83. package/dist/tests/actions.test.js +2 -2
  84. package/dist/tests/actions.test.js.map +1 -1
  85. package/dist/tests/auth.test.js +193 -46
  86. package/dist/tests/auth.test.js.map +1 -1
  87. package/dist/tests/calc.test.js +2 -2
  88. package/dist/tests/calc.test.js.map +1 -1
  89. package/dist/tests/db.test.d.ts.map +1 -1
  90. package/dist/tests/db.test.js.map +1 -1
  91. package/dist/tests/exact_views.test.js +1 -1
  92. package/dist/tests/exact_views.test.js.map +1 -1
  93. package/dist/tests/field.test.js +3 -1
  94. package/dist/tests/field.test.js.map +1 -1
  95. package/dist/tests/mocks.d.ts +6 -1
  96. package/dist/tests/mocks.d.ts.map +1 -1
  97. package/dist/tests/mocks.js +23 -4
  98. package/dist/tests/mocks.js.map +1 -1
  99. package/dist/tests/remote_query_helper.d.ts.map +1 -1
  100. package/dist/tests/remote_query_helper.js +3 -0
  101. package/dist/tests/remote_query_helper.js.map +1 -1
  102. package/dist/tests/table.test.js +36 -9
  103. package/dist/tests/table.test.js.map +1 -1
  104. package/dist/tests/user.test.js +8 -0
  105. package/dist/tests/user.test.js.map +1 -1
  106. package/dist/tests/view.test.js +1 -1
  107. package/dist/tests/view.test.js.map +1 -1
  108. package/dist/utils.d.ts +1 -1
  109. package/dist/utils.js.map +1 -1
  110. package/package.json +10 -10
@@ -37,7 +37,7 @@ const field_1 = __importDefault(require("./field"));
37
37
  const common_types_1 = require("@saltcorn/types/common_types");
38
38
  const trigger_1 = __importDefault(require("./trigger"));
39
39
  const expression_1 = __importDefault(require("./expression"));
40
- const { apply_calculated_fields, apply_calculated_fields_stored, recalculate_for_stored, get_expression_function, freeVariables, } = expression_1.default;
40
+ const { apply_calculated_fields, apply_calculated_fields_stored, recalculate_for_stored, get_expression_function, freeVariables, add_free_variables_to_joinfields, } = expression_1.default;
41
41
  const csvtojson_1 = __importDefault(require("csvtojson"));
42
42
  const moment_1 = __importDefault(require("moment"));
43
43
  const fs_1 = require("fs");
@@ -45,9 +45,7 @@ const promises_1 = require("fs/promises");
45
45
  const utils_1 = __importDefault(require("../utils"));
46
46
  //import { num_between } from "@saltcorn/types/generators";
47
47
  //import { devNull } from "os";
48
- const { prefixFieldsInWhere } = utils_1.default;
49
- const { InvalidConfiguration, InvalidAdminAction, satisfies, structuredClone, getLines, } = require("../utils");
50
- //import type Tag from "../models/tag";
48
+ const { prefixFieldsInWhere, InvalidConfiguration, InvalidAdminAction, satisfies, structuredClone, getLines, mergeIntoWhere, } = utils_1.default;
51
49
  /**
52
50
  * Transponce Objects
53
51
  * TODO more detailed explanation
@@ -114,8 +112,11 @@ class Table {
114
112
  this.is_user_group = !!o.is_user_group;
115
113
  this.external = false;
116
114
  this.description = o.description;
117
- if (o.fields)
118
- this.fields = o.fields.map((f) => new field_1.default(f));
115
+ if (!o.fields) {
116
+ console.trace("missing fields", o);
117
+ throw new Error("missing fields");
118
+ }
119
+ this.fields = o.fields.map((f) => new field_1.default(f));
119
120
  }
120
121
  /**
121
122
  *
@@ -160,7 +161,13 @@ class Table {
160
161
  return getState().tables.map((t) => new Table(t));
161
162
  }
162
163
  const tbls = await db_1.default.select("_sc_tables", where, selectopts);
163
- return tbls.map((t) => new Table(t));
164
+ const flds = await db_1.default.select("_sc_fields", db_1.default.isSQLite ? {} : { table_id: { in: tbls.map((t) => t.id) } }, selectopts);
165
+ return tbls.map((t) => {
166
+ t.fields = flds
167
+ .filter((f) => f.table_id === t.id)
168
+ .map((f) => new field_1.default(f));
169
+ return new Table(t);
170
+ });
164
171
  }
165
172
  /**
166
173
  * Find Tables including external tables
@@ -179,7 +186,15 @@ class Table {
179
186
  if (external !== true) {
180
187
  //do include db tables
181
188
  const tbls = await db_1.default.select("_sc_tables", where, selectopts);
182
- dbs = tbls.map((t) => new Table(t));
189
+ const flds = await db_1.default.select("_sc_fields", db_1.default.isSQLite
190
+ ? {}
191
+ : { table_id: { in: tbls.map((t) => t.id) } }, selectopts);
192
+ dbs = tbls.map((t) => {
193
+ t.fields = flds
194
+ .filter((f) => f.table_id === t.id)
195
+ .map((f) => new field_1.default(f));
196
+ return new Table(t);
197
+ });
183
198
  }
184
199
  return [...dbs, ...externals];
185
200
  }
@@ -242,7 +257,7 @@ class Table {
242
257
  if (ofield)
243
258
  opts.push({
244
259
  label: `Inherit ${field.label}`,
245
- value: `Fml:${field.name}.${ofield.name}===user.id`,
260
+ value: `Fml:${field.name}?.${ofield.name}===user.id`,
246
261
  });
247
262
  }
248
263
  if (refTable?.ownership_formula) {
@@ -256,7 +271,7 @@ class Table {
256
271
  if (fldNms.has(path[0])) {
257
272
  opts.push({
258
273
  label: `Inherit ${field.label}`,
259
- value: `Fml:${field.name}.${refFml}`,
274
+ value: `Fml:${field.name}?.${refFml}`,
260
275
  });
261
276
  }
262
277
  }
@@ -271,7 +286,7 @@ class Table {
271
286
  });
272
287
  }
273
288
  else {
274
- const fml = refFml.replace(`.includes(${ref})`, `.includes(${field.name}.${ref})`);
289
+ const fml = refFml.replace(`.includes(${ref})`, `.includes(${field.name}?.${ref})`);
275
290
  opts.push({
276
291
  label: `Inherit ${field.label}`,
277
292
  value: `Fml:${fml}`,
@@ -333,15 +348,32 @@ class Table {
333
348
  ownership_formula: options.ownership_formula,
334
349
  description: options.description || "",
335
350
  };
351
+ let pk_fld_id;
336
352
  if (!id) {
337
353
  // insert table definition into _sc_tables
338
354
  id = await db_1.default.insert("_sc_tables", tblrow);
339
355
  // add primary key column ID
340
- await db_1.default.query(`insert into ${schema}_sc_fields(table_id, name, label, type, attributes, required, is_unique,primary_key)
341
- values($1,'id','ID','Integer', '{}', true, true, true)`, [id]);
356
+ const insfldres = await db_1.default.query(`insert into ${schema}_sc_fields(table_id, name, label, type, attributes, required, is_unique,primary_key)
357
+ values($1,'id','ID','Integer', '{}', true, true, true) returning id`, [id]);
358
+ pk_fld_id = insfldres.rows[0].id;
342
359
  }
343
360
  // create table
344
- const table = new Table({ ...tblrow, id });
361
+ const table = new Table({
362
+ ...tblrow,
363
+ id,
364
+ fields: [
365
+ new field_1.default({
366
+ type: "Integer",
367
+ name: "id",
368
+ label: "ID",
369
+ primary_key: true,
370
+ required: true,
371
+ is_unique: true,
372
+ table_id: id,
373
+ id: pk_fld_id,
374
+ }),
375
+ ],
376
+ });
345
377
  // create table history
346
378
  if (table.versioned)
347
379
  await table.create_history_table();
@@ -398,20 +430,51 @@ class Table {
398
430
  pk.type.name === "Integer")
399
431
  await db_1.default.reset_sequence(this.name);
400
432
  }
433
+ updateWhereWithOwnership(where, fields, user) {
434
+ const role = user?.role_id;
435
+ if (role &&
436
+ role > this.min_role_write &&
437
+ ((!this.ownership_field_id && !this.ownership_formula) || role === 10))
438
+ return { notAuthorized: true };
439
+ if (user &&
440
+ role < 10 &&
441
+ role > this.min_role_write &&
442
+ this.ownership_field_id) {
443
+ const owner_field = fields.find((f) => f.id === this.ownership_field_id);
444
+ if (!owner_field)
445
+ throw new Error(`Owner field in table ${this.name} not found`);
446
+ mergeIntoWhere(where, {
447
+ [owner_field.name]: user.id,
448
+ });
449
+ }
450
+ }
401
451
  /**
402
452
  * Delete rows from table
403
453
  * @param where - condition
404
454
  * @returns {Promise<void>}
405
455
  */
406
- async deleteRows(where) {
456
+ async deleteRows(where, user) {
407
457
  // get triggers on delete
408
458
  const triggers = await trigger_1.default.getTableTriggers("Delete", this);
409
459
  const fields = await this.getFields();
460
+ if (this.updateWhereWithOwnership(where, fields, user)?.notAuthorized) {
461
+ return;
462
+ }
463
+ let rows;
464
+ if (user && user.role_id > this.min_role_write && this.ownership_formula) {
465
+ rows = await this.getJoinedRows({
466
+ where,
467
+ forUser: user,
468
+ });
469
+ }
410
470
  const deleteFileFields = fields.filter((f) => f.type === "File" && f.attributes?.also_delete_file);
411
471
  const deleteFiles = [];
412
472
  if (triggers.length > 0 || deleteFileFields.length > 0) {
413
473
  const File = require("./file");
414
- const rows = await this.getRows(where);
474
+ if (!rows)
475
+ rows = await this.getJoinedRows({
476
+ where,
477
+ });
415
478
  for (const trigger of triggers) {
416
479
  for (const row of rows) {
417
480
  // run triggers on delete
@@ -427,7 +490,12 @@ class Table {
427
490
  }
428
491
  }
429
492
  }
430
- await db_1.default.deleteWhere(this.name, where);
493
+ if (rows)
494
+ await db_1.default.deleteWhere(this.name, {
495
+ [this.pk_name]: { in: rows.map((r) => r[this.pk_name]) },
496
+ });
497
+ else
498
+ await db_1.default.deleteWhere(this.name, where);
431
499
  await this.resetSequence();
432
500
  for (const file of deleteFiles) {
433
501
  await file.delete();
@@ -454,10 +522,30 @@ class Table {
454
522
  * @returns {Promise<null|*>}
455
523
  */
456
524
  async getRow(where = {}, selopts = {}) {
457
- await this.getFields();
458
- const row = await db_1.default.selectMaybeOne(this.name, where, selopts);
525
+ const fields = await this.getFields();
526
+ const { forUser, forPublic, ...selopts1 } = selopts;
527
+ const role = forUser ? forUser.role_id : forPublic ? 10 : null;
528
+ const row = await db_1.default.selectMaybeOne(this.name, where, selopts1);
459
529
  if (!row || !this.fields)
460
530
  return null;
531
+ if (role && role > this.min_role_read) {
532
+ //check ownership
533
+ if (forPublic)
534
+ return null;
535
+ else if (this.ownership_field_id) {
536
+ const owner_field = fields.find((f) => f.id === this.ownership_field_id);
537
+ if (!owner_field)
538
+ throw new Error(`Owner field in table ${this.name} not found`);
539
+ if (row[owner_field.name] !== forUser.id)
540
+ return null;
541
+ }
542
+ else if (this.ownership_formula) {
543
+ if (!this.is_owner(forUser, row))
544
+ return null;
545
+ }
546
+ else
547
+ return null; //no ownership
548
+ }
461
549
  return apply_calculated_fields([this.readFromDB(row)], this.fields)[0];
462
550
  }
463
551
  /**
@@ -467,10 +555,30 @@ class Table {
467
555
  * @returns {Promise<void>}
468
556
  */
469
557
  async getRows(where = {}, selopts = {}) {
470
- await this.getFields();
471
- const rows = await db_1.default.select(this.name, where, selopts);
558
+ const fields = await this.getFields();
472
559
  if (!this.fields)
473
560
  return [];
561
+ const { forUser, forPublic, ...selopts1 } = selopts;
562
+ const role = forUser ? forUser.role_id : forPublic ? 10 : null;
563
+ if (role &&
564
+ this.updateWhereWithOwnership(where, fields, forUser || { role_id: 10 })
565
+ ?.notAuthorized) {
566
+ return [];
567
+ }
568
+ let rows = await db_1.default.select(this.name, where, selopts1);
569
+ if (role && role > this.min_role_read) {
570
+ //check ownership
571
+ if (forPublic)
572
+ return [];
573
+ else if (this.ownership_field_id) {
574
+ //already dealt with by changing where
575
+ }
576
+ else if (this.ownership_formula) {
577
+ rows = rows.filter((row) => this.is_owner(forUser, row));
578
+ }
579
+ else
580
+ return []; //no ownership
581
+ }
474
582
  return apply_calculated_fields(rows.map((r) => this.readFromDB(r)), this.fields);
475
583
  }
476
584
  /**
@@ -515,6 +623,7 @@ class Table {
515
623
  let v = { ...v_in };
516
624
  const fields = await this.getFields();
517
625
  const pk_name = this.pk_name;
626
+ const role = user?.role_id;
518
627
  if (fields.some((f) => f.calculated && f.stored)) {
519
628
  const joinFields = this.storedExpressionJoinFields();
520
629
  //if any freevars are join fields, update row in db first
@@ -523,10 +632,10 @@ class Table {
523
632
  if (need_to_update) {
524
633
  await db_1.default.update(this.name, v, id, { pk_name });
525
634
  }
526
- existing = (await this.getJoinedRows({
635
+ existing = await this.getJoinedRow({
527
636
  where: { [pk_name]: id },
528
637
  joinFields,
529
- }))[0];
638
+ });
530
639
  let calced = await apply_calculated_fields_stored(need_to_update ? existing : { ...existing, ...v_in },
531
640
  // @ts-ignore TODO ch throw ?
532
641
  this.fields);
@@ -534,11 +643,42 @@ class Table {
534
643
  if (f.calculated && f.stored)
535
644
  v[f.name] = calced[f.name];
536
645
  }
646
+ if (user && role && role > this.min_role_write) {
647
+ if (role === 10)
648
+ return;
649
+ if (this.ownership_field_id) {
650
+ const owner_field = fields.find((f) => f.id === this.ownership_field_id);
651
+ if (!owner_field)
652
+ throw new Error(`Owner field in table ${this.name} not found`);
653
+ if (v[owner_field.name] && v[owner_field.name] !== user.id)
654
+ return;
655
+ //need to check existing
656
+ if (!existing)
657
+ existing = await this.getJoinedRow({
658
+ where: { [pk_name]: id },
659
+ forUser: user,
660
+ });
661
+ if (!existing || existing?.[owner_field.name] !== user.id)
662
+ return;
663
+ }
664
+ if (this.ownership_formula) {
665
+ if (!existing)
666
+ existing = await this.getJoinedRow({
667
+ where: { [pk_name]: id },
668
+ forUser: user,
669
+ });
670
+ if (!existing || !this.is_owner(user, existing))
671
+ return;
672
+ }
673
+ if (!this.ownership_field_id && !this.ownership_formula)
674
+ return;
675
+ }
537
676
  if (this.versioned) {
677
+ const existing1 = await db_1.default.selectOne(this.name, { [pk_name]: id });
538
678
  if (!existing)
539
- existing = await db_1.default.selectOne(this.name, { [pk_name]: id });
679
+ existing = existing1;
540
680
  await db_1.default.insert(this.name + "__history", {
541
- ...existing,
681
+ ...existing1,
542
682
  ...v,
543
683
  [pk_name]: id,
544
684
  _version: {
@@ -556,7 +696,7 @@ class Table {
556
696
  }
557
697
  const newRow = { ...existing, ...v, [pk_name]: id };
558
698
  if (!noTrigger) {
559
- const trigPromise = trigger_1.default.runTableTriggers("Update", this, newRow, resultCollector, user);
699
+ const trigPromise = trigger_1.default.runTableTriggers("Update", this, newRow, resultCollector, role === 10 ? undefined : user);
560
700
  if (resultCollector)
561
701
  await trigPromise;
562
702
  }
@@ -623,13 +763,24 @@ class Table {
623
763
  const fields = await this.getFields();
624
764
  const pk_name = this.pk_name;
625
765
  const joinFields = this.storedExpressionJoinFields();
626
- let v;
627
- let id;
766
+ let v, id;
767
+ if (user && user.role_id > this.min_role_write) {
768
+ if (this.ownership_field_id) {
769
+ const owner_field = fields.find((f) => f.id === this.ownership_field_id);
770
+ if (!owner_field)
771
+ throw new Error(`Owner field in table ${this.name} not found`);
772
+ if (v_in[owner_field.name] !== user.id)
773
+ return;
774
+ }
775
+ if (!this.ownership_field_id && !this.ownership_formula)
776
+ return;
777
+ }
628
778
  if (Object.keys(joinFields).length > 0) {
629
779
  id = await db_1.default.insert(this.name, v_in, { pk_name });
630
780
  let existing = await this.getJoinedRows({
631
781
  where: { [pk_name]: id },
632
782
  joinFields,
783
+ forUser: user,
633
784
  });
634
785
  let calced = await apply_calculated_fields_stored(existing[0], fields);
635
786
  v = { ...v_in };
@@ -642,6 +793,16 @@ class Table {
642
793
  v = await apply_calculated_fields_stored(v_in, fields);
643
794
  id = await db_1.default.insert(this.name, v, { pk_name });
644
795
  }
796
+ if (user && user.role_id > this.min_role_write && this.ownership_formula) {
797
+ let existing = await this.getJoinedRow({
798
+ where: { [pk_name]: id },
799
+ forUser: user,
800
+ });
801
+ if (!existing || !this.is_owner(user, existing)) {
802
+ await this.deleteRows({ [pk_name]: id });
803
+ return;
804
+ }
805
+ }
645
806
  if (this.versioned)
646
807
  await db_1.default.insert(this.name + "__history", {
647
808
  ...v,
@@ -677,13 +838,7 @@ class Table {
677
838
  * Get Fields list for table
678
839
  * @returns {Promise<Field[]>}
679
840
  */
680
- async getFields() {
681
- if (!this.fields) {
682
- this.fields = await field_1.default.find({ table_id: this.id }, { orderBy: "id" });
683
- for (let field of this.fields) {
684
- field.table = this;
685
- }
686
- }
841
+ getFields() {
687
842
  return this.fields;
688
843
  }
689
844
  /**
@@ -692,7 +847,17 @@ class Table {
692
847
  */
693
848
  async getField(path) {
694
849
  const fields = await this.getFields();
695
- if (path.includes(".")) {
850
+ if (path.includes("->")) {
851
+ const joinPath = path.split(".");
852
+ const tableName = joinPath[0];
853
+ const joinTable = await Table.findOne({ name: tableName });
854
+ if (!joinTable)
855
+ throw new Error(`The table '${tableName}' does not exist.`);
856
+ const joinedField = joinPath[1].split("->")[1];
857
+ const fields = await joinTable.getFields();
858
+ return fields.find((f) => f.name === joinedField);
859
+ }
860
+ else if (path.includes(".")) {
696
861
  const keypath = path.split(".");
697
862
  let field, theFields = fields;
698
863
  for (let i = 0; i < keypath.length; i++) {
@@ -1119,6 +1284,106 @@ class Table {
1119
1284
  success: `Imported ${file_rows.length} rows into table ${this.name}`,
1120
1285
  };
1121
1286
  }
1287
+ /**
1288
+ * get join-field-options joined from a field in this table
1289
+ * @param allow_double
1290
+ * @param allow_triple
1291
+ * @returns
1292
+ */
1293
+ async get_join_field_options(allow_double, allow_triple) {
1294
+ const fields = await this.getFields();
1295
+ const result = [];
1296
+ for (const f of fields) {
1297
+ if (f.is_fkey && f.type !== "File") {
1298
+ const table = await Table.findOne({ name: f.reftable_name });
1299
+ if (!table)
1300
+ throw new Error(`Unable to find table '${f.reftable_name}`);
1301
+ await table.getFields();
1302
+ if (!table.fields)
1303
+ throw new Error(`The table '${f.reftable_name} has no fields.`);
1304
+ const subOne = {
1305
+ name: f.name,
1306
+ table: table.name,
1307
+ subFields: new Array(),
1308
+ fieldPath: f.name,
1309
+ };
1310
+ for (const pf of table.fields.filter((f) => !f.calculated || f.stored)) {
1311
+ const subTwo = {
1312
+ name: pf.name,
1313
+ subFields: new Array(),
1314
+ fieldPath: `${f.name}.${pf.name}`,
1315
+ };
1316
+ if (pf.is_fkey && pf.type !== "File" && allow_double) {
1317
+ const table1 = await Table.findOne({ name: pf.reftable_name });
1318
+ if (!table1)
1319
+ throw new Error(`Unable to find table '${pf.reftable_name}`);
1320
+ await table1.getFields();
1321
+ subTwo.table = table1.name;
1322
+ if (!table1.fields)
1323
+ throw new Error(`The table '${pf.reftable_name} has no fields.`);
1324
+ if (table1.fields)
1325
+ for (const gpf of table1.fields.filter((f) => !f.calculated || f.stored)) {
1326
+ const subThree = {
1327
+ name: gpf.name,
1328
+ subFields: new Array(),
1329
+ fieldPath: `${f.name}.${pf.name}.${gpf.name}`,
1330
+ };
1331
+ if (allow_triple && gpf.is_fkey && gpf.type !== "File") {
1332
+ const gpfTbl = Table.findOne({
1333
+ name: gpf.reftable_name,
1334
+ });
1335
+ if (gpfTbl) {
1336
+ subThree.table = gpfTbl.name;
1337
+ const gpfFields = await gpfTbl.getFields();
1338
+ for (const ggpf of gpfFields.filter((f) => !f.calculated || f.stored)) {
1339
+ subThree.subFields.push({
1340
+ name: ggpf.name,
1341
+ fieldPath: `${f.name}.${pf.name}.${gpf.name}.${ggpf.name}`,
1342
+ });
1343
+ }
1344
+ }
1345
+ }
1346
+ subTwo.subFields.push(subThree);
1347
+ }
1348
+ }
1349
+ subOne.subFields.push(subTwo);
1350
+ }
1351
+ result.push(subOne);
1352
+ }
1353
+ }
1354
+ return result;
1355
+ }
1356
+ /**
1357
+ * get relation-options joined from a field of another table
1358
+ * @returns
1359
+ */
1360
+ async get_relation_options() {
1361
+ return await Promise.all((await this.get_relation_data()).map(async ({ relationTable, relationField }) => {
1362
+ const path = `${relationTable.name}.${relationField.name}`;
1363
+ const relFields = await relationTable.getFields();
1364
+ const names = relFields
1365
+ .filter((f) => f.type !== "Key")
1366
+ .map((f) => f.name);
1367
+ return { relationPath: path, relationFields: names };
1368
+ }));
1369
+ }
1370
+ /**
1371
+ * get relation-data joined from a field of another table
1372
+ * @returns
1373
+ */
1374
+ async get_relation_data() {
1375
+ const result = new Array();
1376
+ const o2o_rels = await field_1.default.find({
1377
+ reftable_name: this.name,
1378
+ is_unique: true,
1379
+ });
1380
+ for (const field of o2o_rels) {
1381
+ const relTbl = Table.findOne({ id: field.table_id });
1382
+ if (relTbl)
1383
+ result.push({ relationTable: relTbl, relationField: field });
1384
+ }
1385
+ return result;
1386
+ }
1122
1387
  /**
1123
1388
  * Get parent relations for table
1124
1389
  * @param allow_double
@@ -1250,6 +1515,24 @@ class Table {
1250
1515
  let joinFields = opts.joinFields || {};
1251
1516
  let aggregations = opts.aggregations || {};
1252
1517
  const schema = db_1.default.getTenantSchemaPrefix();
1518
+ const { forUser, forPublic } = opts;
1519
+ const role = forUser ? forUser.role_id : forPublic ? 10 : null;
1520
+ if (role && role > this.min_role_read && this.ownership_formula) {
1521
+ const freeVars = freeVariables(this.ownership_formula);
1522
+ add_free_variables_to_joinfields(freeVars, joinFields, fields);
1523
+ }
1524
+ if (role && role > this.min_role_read && this.ownership_field_id) {
1525
+ if (forPublic)
1526
+ return { notAuthorized: true };
1527
+ const owner_field = fields.find((f) => f.id === this.ownership_field_id);
1528
+ if (!owner_field)
1529
+ throw new Error(`Owner field in table ${this.name} not found`);
1530
+ if (!opts.where)
1531
+ opts.where = {};
1532
+ mergeIntoWhere(opts.where, {
1533
+ [owner_field.name]: forUser.id,
1534
+ });
1535
+ }
1253
1536
  for (const [fldnm, { ref, target, through, ontable }] of Object.entries(joinFields)) {
1254
1537
  let reffield;
1255
1538
  if (ontable) {
@@ -1361,7 +1644,15 @@ class Table {
1361
1644
  offset: opts.offset,
1362
1645
  };
1363
1646
  const sql = `SELECT ${fldNms.join()} FROM ${schema}"${(0, internal_1.sqlsanitize)(this.name)}" a ${joinq} ${where} ${(0, internal_1.mkSelectOptions)(selectopts)}`;
1364
- return { sql, values };
1647
+ return { sql, values, joinFields };
1648
+ }
1649
+ /**
1650
+ * @param {object} [opts = {}]
1651
+ * @returns {Promise<object[]>}
1652
+ */
1653
+ async getJoinedRow(opts = {}) {
1654
+ const rows = await this.getJoinedRows(opts);
1655
+ return rows.length > 0 ? rows[0] : null;
1365
1656
  }
1366
1657
  /**
1367
1658
  * @param {object} [opts = {}]
@@ -1369,17 +1660,19 @@ class Table {
1369
1660
  */
1370
1661
  async getJoinedRows(opts = {}) {
1371
1662
  const fields = await this.getFields();
1372
- const { sql, values } = await this.getJoinedQuery(opts);
1663
+ const { forUser, forPublic, ...selopts1 } = opts;
1664
+ const role = forUser ? forUser.role_id : forPublic ? 10 : null;
1665
+ const { sql, values, notAuthorized, joinFields } = await this.getJoinedQuery(opts);
1666
+ if (notAuthorized)
1667
+ return [];
1373
1668
  const res = await db_1.default.query(sql, values);
1374
1669
  if (res.length === 0)
1375
1670
  return res; // check
1376
- //console.log(sql);
1377
- //console.log(res.rows);
1378
- const calcRow = apply_calculated_fields(res.rows, fields);
1671
+ let calcRow = apply_calculated_fields(res.rows, fields);
1379
1672
  //rename joinfields
1380
- if (Object.values(opts.joinFields || {}).some((jf) => jf.rename_object)) {
1673
+ if (Object.values(joinFields || {}).some((jf) => jf.rename_object)) {
1381
1674
  let f = (x) => x;
1382
- Object.entries(opts.joinFields || {}).forEach(([k, v]) => {
1675
+ Object.entries(joinFields || {}).forEach(([k, v]) => {
1383
1676
  if (v.rename_object) {
1384
1677
  if (v.rename_object.length === 2) {
1385
1678
  const oldf = f;
@@ -1428,10 +1721,22 @@ class Table {
1428
1721
  }
1429
1722
  }
1430
1723
  });
1431
- return calcRow.map(f);
1724
+ calcRow = calcRow.map(f);
1432
1725
  }
1433
- else
1434
- return calcRow;
1726
+ if (role && role > this.min_role_read) {
1727
+ //check ownership
1728
+ if (forPublic)
1729
+ return [];
1730
+ else if (this.ownership_field_id) {
1731
+ //already dealt with by changing where
1732
+ }
1733
+ else if (this.ownership_formula) {
1734
+ calcRow = calcRow.filter((row) => this.is_owner(forUser, row));
1735
+ }
1736
+ else
1737
+ return []; //no ownership
1738
+ }
1739
+ return calcRow;
1435
1740
  }
1436
1741
  async slug_options() {
1437
1742
  const fields = await this.getFields();