@saltcorn/data 0.8.7 → 0.8.8-beta.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 (68) hide show
  1. package/dist/base-plugin/actions.d.ts.map +1 -1
  2. package/dist/base-plugin/actions.js +2 -3
  3. package/dist/base-plugin/actions.js.map +1 -1
  4. package/dist/base-plugin/index.d.ts +5 -8
  5. package/dist/base-plugin/index.d.ts.map +1 -1
  6. package/dist/base-plugin/viewtemplates/edit.d.ts +2 -1
  7. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  8. package/dist/base-plugin/viewtemplates/edit.js +14 -3
  9. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  10. package/dist/base-plugin/viewtemplates/filter.d.ts +3 -7
  11. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  12. package/dist/base-plugin/viewtemplates/filter.js +145 -20
  13. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  14. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  15. package/dist/base-plugin/viewtemplates/viewable_fields.js +4 -2
  16. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  17. package/dist/db/state.d.ts +1 -0
  18. package/dist/db/state.d.ts.map +1 -1
  19. package/dist/db/state.js +18 -0
  20. package/dist/db/state.js.map +1 -1
  21. package/dist/migrations/202305031518.d.ts +3 -0
  22. package/dist/migrations/202305031518.d.ts.map +1 -0
  23. package/dist/migrations/202305031518.js +52 -0
  24. package/dist/migrations/202305031518.js.map +1 -0
  25. package/dist/model-helper.d.ts +20 -0
  26. package/dist/model-helper.d.ts.map +1 -0
  27. package/dist/model-helper.js +111 -0
  28. package/dist/model-helper.js.map +1 -0
  29. package/dist/models/config.d.ts +1 -0
  30. package/dist/models/config.d.ts.map +1 -1
  31. package/dist/models/config.js +1 -0
  32. package/dist/models/config.js.map +1 -1
  33. package/dist/models/crash.js +1 -1
  34. package/dist/models/crash.js.map +1 -1
  35. package/dist/models/expression.d.ts.map +1 -1
  36. package/dist/models/expression.js +8 -6
  37. package/dist/models/expression.js.map +1 -1
  38. package/dist/models/index.d.ts +1 -0
  39. package/dist/models/index.d.ts.map +1 -1
  40. package/dist/models/model.d.ts +54 -0
  41. package/dist/models/model.d.ts.map +1 -0
  42. package/dist/models/model.js +172 -0
  43. package/dist/models/model.js.map +1 -0
  44. package/dist/models/model_instance.d.ts +57 -0
  45. package/dist/models/model_instance.d.ts.map +1 -0
  46. package/dist/models/model_instance.js +122 -0
  47. package/dist/models/model_instance.js.map +1 -0
  48. package/dist/models/table.d.ts +4 -1
  49. package/dist/models/table.d.ts.map +1 -1
  50. package/dist/models/table.js +105 -40
  51. package/dist/models/table.js.map +1 -1
  52. package/dist/models/trigger.d.ts.map +1 -1
  53. package/dist/models/trigger.js +1 -0
  54. package/dist/models/trigger.js.map +1 -1
  55. package/dist/models/user.d.ts.map +1 -1
  56. package/dist/models/user.js +6 -6
  57. package/dist/models/user.js.map +1 -1
  58. package/dist/plugin-helper.d.ts +2 -2
  59. package/dist/plugin-helper.d.ts.map +1 -1
  60. package/dist/plugin-helper.js +39 -32
  61. package/dist/plugin-helper.js.map +1 -1
  62. package/dist/tests/models.test.js +35 -0
  63. package/dist/tests/models.test.js.map +1 -1
  64. package/dist/tests/table.test.js +67 -5
  65. package/dist/tests/table.test.js.map +1 -1
  66. package/dist/tests/user.test.js +87 -1
  67. package/dist/tests/user.test.js.map +1 -1
  68. package/package.json +8 -8
@@ -233,6 +233,13 @@ class Table {
233
233
  }
234
234
  return [...dbs, ...externals];
235
235
  }
236
+ async get_models(opts) {
237
+ const Model = require("./model");
238
+ if (typeof opts === "string")
239
+ return await Model.find({ name: opts, table_id: this.id });
240
+ else
241
+ return await Model.find({ ...(opts || {}), table_id: this.id });
242
+ }
236
243
  /**
237
244
  * Get owner column name
238
245
  * @param fields - fields list
@@ -275,7 +282,7 @@ class Table {
275
282
  return typeof field_name === "string" && row[field_name] === user.id;
276
283
  }
277
284
  async ownership_options() {
278
- const fields = await this.getFields();
285
+ const fields = this.fields;
279
286
  //start with userfields
280
287
  const opts = fields
281
288
  .filter((f) => f.reftable_name === "users")
@@ -491,7 +498,7 @@ class Table {
491
498
  return `${db_1.default.getTenantSchemaPrefix()}"${(0, internal_1.sqlsanitize)(this.name)}"`;
492
499
  }
493
500
  async resetSequence() {
494
- const fields = await this.getFields();
501
+ const fields = this.fields;
495
502
  const pk = fields.find((f) => f.primary_key);
496
503
  if (!pk) {
497
504
  throw new Error("Unable to find a field with a primary key.");
@@ -526,7 +533,7 @@ class Table {
526
533
  async deleteRows(where, user) {
527
534
  // get triggers on delete
528
535
  const triggers = await trigger_1.default.getTableTriggers("Delete", this);
529
- const fields = await this.getFields();
536
+ const fields = this.fields;
530
537
  if (this.updateWhereWithOwnership(where, fields, user)?.notAuthorized) {
531
538
  const state = require("../db/state").getState();
532
539
  state.log(4, `Not authorized to deleteRows in table ${this.name}.`);
@@ -595,7 +602,7 @@ class Table {
595
602
  * @returns {Promise<null|*>}
596
603
  */
597
604
  async getRow(where = {}, selopts = {}) {
598
- const fields = await this.getFields();
605
+ const fields = this.fields;
599
606
  const { forUser, forPublic, ...selopts1 } = selopts;
600
607
  const role = forUser ? forUser.role_id : forPublic ? 100 : null;
601
608
  const row = await db_1.default.selectMaybeOne(this.name, where, selopts1);
@@ -628,7 +635,7 @@ class Table {
628
635
  * @returns {Promise<void>}
629
636
  */
630
637
  async getRows(where = {}, selopts = {}) {
631
- const fields = await this.getFields();
638
+ const fields = this.fields;
632
639
  if (!this.fields)
633
640
  return [];
634
641
  const { forUser, forPublic, ...selopts1 } = selopts;
@@ -700,7 +707,7 @@ class Table {
700
707
  async updateRow(v_in, id, user, noTrigger, resultCollector, restore_of_version) {
701
708
  let existing;
702
709
  let v = { ...v_in };
703
- const fields = await this.getFields();
710
+ const fields = this.fields;
704
711
  const pk_name = this.pk_name;
705
712
  const role = user?.role_id;
706
713
  const state = require("../db/state").getState();
@@ -710,9 +717,11 @@ class Table {
710
717
  }
711
718
  if (this.ownership_formula)
712
719
  add_free_variables_to_joinfields(freeVariables(this.ownership_formula), joinFields, fields);
713
- if (user && role && role > this.min_role_write) {
714
- if (role === 10)
715
- return "Not authorized";
720
+ if (user &&
721
+ role &&
722
+ (role > this.min_role_write || role > this.min_role_read)) {
723
+ if (role === 100)
724
+ return "Not authorized"; //no possibility of ownership
716
725
  if (this.ownership_field_id) {
717
726
  const owner_field = fields.find((f) => f.id === this.ownership_field_id);
718
727
  if (!owner_field)
@@ -762,6 +771,11 @@ class Table {
762
771
  if (constraint_check)
763
772
  return constraint_check;
764
773
  }
774
+ if (user) {
775
+ let field_write_check = this.check_field_write_role(v, user);
776
+ if (field_write_check)
777
+ return field_write_check;
778
+ }
765
779
  if (fields.some((f) => f.calculated && f.stored)) {
766
780
  //if any freevars are join fields, update row in db first
767
781
  const freeVarFKFields = new Set(Object.values(joinFields).map((jf) => jf.ref));
@@ -837,7 +851,7 @@ class Table {
837
851
  return { success: true };
838
852
  }
839
853
  catch (e) {
840
- return { error: normalise_error_message(e.message) };
854
+ return { error: this.normalise_error_message(e.message) };
841
855
  }
842
856
  }
843
857
  /**
@@ -846,22 +860,10 @@ class Table {
846
860
  * @param field_name
847
861
  * @returns {Promise<void>}
848
862
  */
849
- async toggleBool(id, field_name) {
850
- const schema = db_1.default.getTenantSchemaPrefix();
851
- await db_1.default.query(`update ${schema}"${(0, internal_1.sqlsanitize)(this.name)}" set "${(0, internal_1.sqlsanitize)(field_name)}"=NOT coalesce("${(0, internal_1.sqlsanitize)(field_name)}", false) where id=$1`, [id]);
852
- const fields = await this.getFields();
853
- if (fields.some((f) => f.calculated && f.stored)) {
854
- await this.updateRow({}, id, undefined, false);
855
- }
856
- const triggers = await trigger_1.default.getTableTriggers("Update", this);
857
- if (triggers.length > 0) {
858
- const row = await this.getRow({ id });
859
- if (!row)
860
- throw new Error(`Unable to find row with id: ${id}`);
861
- for (const trigger of triggers) {
862
- trigger.run(row);
863
- }
864
- }
863
+ async toggleBool(id, field_name, user) {
864
+ const row = await this.getRow({ [this.pk_name]: id });
865
+ if (row)
866
+ await this.updateRow({ [field_name]: !row[field_name] }, id, user);
865
867
  }
866
868
  /**
867
869
  * Get primary key field
@@ -884,6 +886,15 @@ class Table {
884
886
  }
885
887
  return undefined;
886
888
  }
889
+ check_field_write_role(row, user) {
890
+ for (const field of this.fields) {
891
+ if (typeof row[field.name] !== "undefined" &&
892
+ field.attributes?.min_role_write &&
893
+ user.role_id > field.attributes?.min_role_write)
894
+ return "Not authorized";
895
+ }
896
+ return undefined;
897
+ }
887
898
  /**
888
899
  * Insert row
889
900
  * @param v_in
@@ -892,7 +903,7 @@ class Table {
892
903
  * @returns {Promise<*>}
893
904
  */
894
905
  async insertRow(v_in, user, resultCollector) {
895
- const fields = await this.getFields();
906
+ const fields = this.fields;
896
907
  const pk_name = this.pk_name;
897
908
  const joinFields = this.storedExpressionJoinFields();
898
909
  if (this.ownership_formula)
@@ -917,6 +928,11 @@ class Table {
917
928
  let constraint_check = this.check_table_constraints(v_in);
918
929
  if (constraint_check)
919
930
  throw new Error(constraint_check);
931
+ if (user) {
932
+ let field_write_check = this.check_field_write_role(v_in, user);
933
+ if (field_write_check)
934
+ return field_write_check;
935
+ }
920
936
  if (Object.keys(joinFields).length > 0) {
921
937
  id = await db_1.default.insert(this.name, v_in, { pk_name });
922
938
  let existing = await this.getJoinedRows({
@@ -979,12 +995,58 @@ class Table {
979
995
  async tryInsertRow(v, user, resultCollector) {
980
996
  try {
981
997
  const id = await this.insertRow(v, user, resultCollector);
998
+ if (id?.includes?.("Not authorized"))
999
+ return { error: id };
1000
+ if (id.error)
1001
+ return id;
982
1002
  return { success: id };
983
1003
  }
984
1004
  catch (e) {
985
- return { error: normalise_error_message(e.message) };
1005
+ return { error: this.normalise_error_message(e.message) };
986
1006
  }
987
1007
  }
1008
+ normalise_error_message(msg) {
1009
+ let fieldnm = "";
1010
+ if (msg.toLowerCase().includes("unique constraint")) {
1011
+ if (db_1.default.isSQLite) {
1012
+ fieldnm = msg.replace(`SQLITE_CONSTRAINT: UNIQUE constraint failed: ${this.name}.`, "");
1013
+ }
1014
+ else {
1015
+ const m = msg.match(/duplicate key value violates unique constraint "(.*?)_(.*?)_unique"/);
1016
+ if (m)
1017
+ fieldnm = m[2];
1018
+ }
1019
+ if (fieldnm) {
1020
+ const field = this.fields.find((f) => f.name === fieldnm);
1021
+ if (field?.attributes?.unique_error_msg)
1022
+ return field?.attributes?.unique_error_msg;
1023
+ else {
1024
+ const tc_unique = this.constraints.find((c) => {
1025
+ if (c.type !== "Unique")
1026
+ return false;
1027
+ let conNm = "";
1028
+ if (db_1.default.isSQLite) {
1029
+ // SQLITE_CONSTRAINT: UNIQUE constraint failed: books.author, books.pages
1030
+ // first table name stripped by replace
1031
+ let [field1, ...rest_fields] = c.configuration.fields;
1032
+ conNm = [
1033
+ field1,
1034
+ ...rest_fields.map((fnm) => `${this.name}.${fnm}`),
1035
+ ].join(", ");
1036
+ }
1037
+ else {
1038
+ conNm = c.configuration.fields.join("_");
1039
+ }
1040
+ return c.configuration.errormsg && conNm === fieldnm;
1041
+ });
1042
+ if (tc_unique)
1043
+ return tc_unique.configuration.errormsg;
1044
+ return `Duplicate value for unique field: ${field?.label || fieldnm}`;
1045
+ }
1046
+ }
1047
+ }
1048
+ return msg;
1049
+ }
988
1050
  /**
989
1051
  * Get Fields list for table
990
1052
  * @returns {Promise<Field[]>}
@@ -1040,7 +1102,7 @@ class Table {
1040
1102
  // todo create function that returns history table name for table
1041
1103
  async create_history_table() {
1042
1104
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
1043
- const fields = await this.getFields();
1105
+ const fields = this.fields;
1044
1106
  const flds = fields.map((f) => `,"${(0, internal_1.sqlsanitize)(f.name)}" ${f.sql_bare_type}`);
1045
1107
  const pk = fields.find((f) => f.primary_key)?.name;
1046
1108
  if (!pk) {
@@ -1182,7 +1244,7 @@ class Table {
1182
1244
  * @returns {Promise<void>}
1183
1245
  */
1184
1246
  async enable_fkey_constraints() {
1185
- const fields = await this.getFields();
1247
+ const fields = this.fields;
1186
1248
  for (const f of fields)
1187
1249
  await f.enable_fkey_constraint(this);
1188
1250
  }
@@ -1570,7 +1632,7 @@ class Table {
1570
1632
  async import_json_file(filePath, skip_first_data_row) {
1571
1633
  // todo argument type buffer is not assignable for type String...
1572
1634
  const file_rows = JSON.parse((await (0, promises_1.readFile)(filePath)).toString());
1573
- const fields = await this.getFields();
1635
+ const fields = this.fields;
1574
1636
  const pk_name = this.pk_name;
1575
1637
  const { readState } = require("../plugin-helper");
1576
1638
  let i = 1;
@@ -1615,7 +1677,7 @@ class Table {
1615
1677
  * @returns
1616
1678
  */
1617
1679
  async get_join_field_options(allow_double, allow_triple) {
1618
- const fields = await this.getFields();
1680
+ const fields = this.fields;
1619
1681
  const result = [];
1620
1682
  for (const f of fields) {
1621
1683
  if (f.is_fkey && f.type !== "File") {
@@ -1715,7 +1777,7 @@ class Table {
1715
1777
  * @returns {Promise<{parent_relations: object[], parent_field_list: object[]}>}
1716
1778
  */
1717
1779
  async get_parent_relations(allow_double, allow_triple) {
1718
- const fields = await this.getFields();
1780
+ const fields = this.fields;
1719
1781
  let parent_relations = [];
1720
1782
  let parent_field_list = [];
1721
1783
  for (const f of fields) {
@@ -1776,7 +1838,7 @@ class Table {
1776
1838
  return { parent_relations, parent_field_list };
1777
1839
  }
1778
1840
  async field_options(nrecurse = 0, fieldWhere = () => true, prefix = "") {
1779
- const fields = await this.getFields();
1841
+ const fields = this.fields;
1780
1842
  const these = fields.filter(fieldWhere).map((f) => prefix + f.name);
1781
1843
  const those = [];
1782
1844
  if (nrecurse > 0)
@@ -1810,7 +1872,7 @@ class Table {
1810
1872
  }
1811
1873
  }
1812
1874
  if (allow_join_aggregations) {
1813
- const fields = await this.getFields();
1875
+ const fields = this.fields;
1814
1876
  for (const f of fields) {
1815
1877
  if (f.is_fkey && f.type !== "File") {
1816
1878
  const refTable = Table.findOne({ name: f.reftable_name });
@@ -1832,7 +1894,7 @@ class Table {
1832
1894
  * @returns {Promise<{values, sql: string}>}
1833
1895
  */
1834
1896
  async getJoinedQuery(opts = {}) {
1835
- const fields = await this.getFields();
1897
+ const fields = this.fields;
1836
1898
  let fldNms = [];
1837
1899
  let joinq = "";
1838
1900
  let joinTables = [];
@@ -1937,6 +1999,9 @@ class Table {
1937
1999
  const aggTable = Table.findOne({ name: table });
1938
2000
  const aggField = aggTable?.fields?.find((f) => f.name === field);
1939
2001
  const ownField = through ? (0, internal_1.sqlsanitize)(through) : this.pk_name;
2002
+ const agg_and_field = aggregate.toLowerCase() === "countunique"
2003
+ ? `count(distinct ${field ? `"${(0, internal_1.sqlsanitize)(field)}"` : "*"})`
2004
+ : `${(0, internal_1.sqlsanitize)(aggregate)}(${field ? `"${(0, internal_1.sqlsanitize)(field)}"` : "*"})`;
1940
2005
  if (aggField?.is_fkey &&
1941
2006
  aggField.attributes.summary_field &&
1942
2007
  aggregate.toLowerCase() === "array_agg") {
@@ -1950,9 +2015,9 @@ class Table {
1950
2015
  fldNms.push(`(select "${(0, internal_1.sqlsanitize)(field)}" from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${dateField}"=(select ${isLatest ? `max` : `min`}("${dateField}") from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${(0, internal_1.sqlsanitize)(ref)}"=a."${ownField}"${whereStr ? ` and ${whereStr}` : ""}) and "${(0, internal_1.sqlsanitize)(ref)}"=a."${ownField}" limit 1) ${(0, internal_1.sqlsanitize)(fldnm)}`);
1951
2016
  }
1952
2017
  else if (subselect)
1953
- fldNms.push(`(select ${(0, internal_1.sqlsanitize)(aggregate)}(${field ? `"${(0, internal_1.sqlsanitize)(field)}"` : "*"}) from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${(0, internal_1.sqlsanitize)(ref)}" in (select "${subselect.field}" from ${schema}"${subselect.table.name}" where "${subselect.whereField}"=a."${ownField}")) ${(0, internal_1.sqlsanitize)(fldnm)}`);
2018
+ fldNms.push(`(select ${agg_and_field} from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${(0, internal_1.sqlsanitize)(ref)}" in (select "${subselect.field}" from ${schema}"${subselect.table.name}" where "${subselect.whereField}"=a."${ownField}")) ${(0, internal_1.sqlsanitize)(fldnm)}`);
1954
2019
  else
1955
- fldNms.push(`(select ${(0, internal_1.sqlsanitize)(aggregate)}(${field ? `"${(0, internal_1.sqlsanitize)(field)}"` : "*"}) from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${(0, internal_1.sqlsanitize)(ref)}"=a."${ownField}"${whereStr ? ` and ${whereStr}` : ""}) ${(0, internal_1.sqlsanitize)(fldnm)}`);
2020
+ fldNms.push(`(select ${agg_and_field} from ${schema}"${(0, internal_1.sqlsanitize)(table)}" where "${(0, internal_1.sqlsanitize)(ref)}"=a."${ownField}"${whereStr ? ` and ${whereStr}` : ""}) ${(0, internal_1.sqlsanitize)(fldnm)}`);
1956
2021
  });
1957
2022
  const selectopts = {
1958
2023
  limit: opts.limit,
@@ -1983,7 +2048,7 @@ class Table {
1983
2048
  * @returns {Promise<object[]>}
1984
2049
  */
1985
2050
  async getJoinedRows(opts = {}) {
1986
- const fields = await this.getFields();
2051
+ const fields = this.fields;
1987
2052
  const { forUser, forPublic, ...selopts1 } = opts;
1988
2053
  const role = forUser ? forUser.role_id : forPublic ? 100 : null;
1989
2054
  const { sql, values, notAuthorized, joinFields } = await this.getJoinedQuery(opts);
@@ -2063,7 +2128,7 @@ class Table {
2063
2128
  return calcRow;
2064
2129
  }
2065
2130
  async slug_options() {
2066
- const fields = await this.getFields();
2131
+ const fields = this.fields;
2067
2132
  const unique_fields = fields.filter((f) => f.is_unique);
2068
2133
  const opts = [];
2069
2134
  unique_fields.forEach((f) => {