@saltcorn/data 0.9.4-beta.8 → 0.9.4

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 (137) hide show
  1. package/dist/base-plugin/actions.d.ts +137 -71
  2. package/dist/base-plugin/actions.d.ts.map +1 -1
  3. package/dist/base-plugin/actions.js +105 -22
  4. package/dist/base-plugin/actions.js.map +1 -1
  5. package/dist/base-plugin/fieldviews.d.ts.map +1 -1
  6. package/dist/base-plugin/fieldviews.js +19 -9
  7. package/dist/base-plugin/fieldviews.js.map +1 -1
  8. package/dist/base-plugin/fileviews.d.ts +13 -0
  9. package/dist/base-plugin/fileviews.js +42 -2
  10. package/dist/base-plugin/fileviews.js.map +1 -1
  11. package/dist/base-plugin/index.d.ts +208 -204
  12. package/dist/base-plugin/index.d.ts.map +1 -1
  13. package/dist/base-plugin/types.d.ts.map +1 -1
  14. package/dist/base-plugin/types.js +25 -10
  15. package/dist/base-plugin/types.js.map +1 -1
  16. package/dist/base-plugin/viewtemplates/edit.d.ts +4 -1
  17. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  18. package/dist/base-plugin/viewtemplates/edit.js +181 -81
  19. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  20. package/dist/base-plugin/viewtemplates/feed.d.ts.map +1 -1
  21. package/dist/base-plugin/viewtemplates/feed.js +4 -1
  22. package/dist/base-plugin/viewtemplates/feed.js.map +1 -1
  23. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  24. package/dist/base-plugin/viewtemplates/filter.js +89 -7
  25. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  26. package/dist/base-plugin/viewtemplates/list.d.ts +3 -2
  27. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  28. package/dist/base-plugin/viewtemplates/list.js +274 -38
  29. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  30. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  31. package/dist/base-plugin/viewtemplates/show.js +116 -56
  32. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  33. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts +4 -3
  34. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  35. package/dist/base-plugin/viewtemplates/viewable_fields.js +173 -57
  36. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  37. package/dist/db/connect.d.ts.map +1 -1
  38. package/dist/db/connect.js +20 -0
  39. package/dist/db/connect.js.map +1 -1
  40. package/dist/db/fixtures.d.ts.map +1 -1
  41. package/dist/db/fixtures.js +196 -30
  42. package/dist/db/fixtures.js.map +1 -1
  43. package/dist/db/state.d.ts +5 -0
  44. package/dist/db/state.d.ts.map +1 -1
  45. package/dist/db/state.js +20 -0
  46. package/dist/db/state.js.map +1 -1
  47. package/dist/migrate.d.ts +1 -0
  48. package/dist/migrate.d.ts.map +1 -1
  49. package/dist/migrate.js +6 -3
  50. package/dist/migrate.js.map +1 -1
  51. package/dist/migrations/202402071125.d.ts +2 -0
  52. package/dist/migrations/202402071125.d.ts.map +1 -0
  53. package/dist/migrations/202402071125.js +4 -0
  54. package/dist/migrations/202402071125.js.map +1 -0
  55. package/dist/models/config.d.ts.map +1 -1
  56. package/dist/models/config.js +27 -0
  57. package/dist/models/config.js.map +1 -1
  58. package/dist/models/email.d.ts.map +1 -1
  59. package/dist/models/email.js +6 -2
  60. package/dist/models/email.js.map +1 -1
  61. package/dist/models/field.d.ts +1 -1
  62. package/dist/models/field.d.ts.map +1 -1
  63. package/dist/models/field.js +4 -4
  64. package/dist/models/field.js.map +1 -1
  65. package/dist/models/file.d.ts +2 -0
  66. package/dist/models/file.d.ts.map +1 -1
  67. package/dist/models/file.js +9 -0
  68. package/dist/models/file.js.map +1 -1
  69. package/dist/models/index.d.ts +1 -1
  70. package/dist/models/internal/query.d.ts +6 -0
  71. package/dist/models/internal/query.d.ts.map +1 -0
  72. package/dist/models/internal/query.js +86 -0
  73. package/dist/models/internal/query.js.map +1 -0
  74. package/dist/models/page.d.ts.map +1 -1
  75. package/dist/models/page.js +4 -0
  76. package/dist/models/page.js.map +1 -1
  77. package/dist/models/page_group.d.ts +1 -0
  78. package/dist/models/page_group.d.ts.map +1 -1
  79. package/dist/models/page_group.js +1 -0
  80. package/dist/models/page_group.js.map +1 -1
  81. package/dist/models/table.d.ts +18 -4
  82. package/dist/models/table.d.ts.map +1 -1
  83. package/dist/models/table.js +84 -52
  84. package/dist/models/table.js.map +1 -1
  85. package/dist/models/tag_entry.d.ts.map +1 -1
  86. package/dist/models/tag_entry.js +9 -0
  87. package/dist/models/tag_entry.js.map +1 -1
  88. package/dist/models/trigger.d.ts +3 -3
  89. package/dist/models/trigger.d.ts.map +1 -1
  90. package/dist/models/trigger.js +69 -7
  91. package/dist/models/trigger.js.map +1 -1
  92. package/dist/models/user.d.ts.map +1 -1
  93. package/dist/models/user.js +5 -4
  94. package/dist/models/user.js.map +1 -1
  95. package/dist/models/view.d.ts +1 -0
  96. package/dist/models/view.d.ts.map +1 -1
  97. package/dist/models/view.js +6 -0
  98. package/dist/models/view.js.map +1 -1
  99. package/dist/plugin-helper.d.ts +7 -19
  100. package/dist/plugin-helper.d.ts.map +1 -1
  101. package/dist/plugin-helper.js +79 -70
  102. package/dist/plugin-helper.js.map +1 -1
  103. package/dist/tests/actions.test.js +143 -2
  104. package/dist/tests/actions.test.js.map +1 -1
  105. package/dist/tests/auth.test.js +101 -1
  106. package/dist/tests/auth.test.js.map +1 -1
  107. package/dist/tests/auxtest.test.js +4 -0
  108. package/dist/tests/auxtest.test.js.map +1 -1
  109. package/dist/tests/calc.test.js +20 -0
  110. package/dist/tests/calc.test.js.map +1 -1
  111. package/dist/tests/edit.test.js +558 -0
  112. package/dist/tests/edit.test.js.map +1 -1
  113. package/dist/tests/filter.test.d.ts +2 -0
  114. package/dist/tests/filter.test.d.ts.map +1 -0
  115. package/dist/tests/filter.test.js +438 -0
  116. package/dist/tests/filter.test.js.map +1 -0
  117. package/dist/tests/list.test.d.ts +2 -0
  118. package/dist/tests/list.test.d.ts.map +1 -0
  119. package/dist/tests/list.test.js +903 -0
  120. package/dist/tests/list.test.js.map +1 -0
  121. package/dist/tests/show.test.d.ts +2 -0
  122. package/dist/tests/show.test.d.ts.map +1 -0
  123. package/dist/tests/show.test.js +325 -0
  124. package/dist/tests/show.test.js.map +1 -0
  125. package/dist/tests/table.test.js +213 -56
  126. package/dist/tests/table.test.js.map +1 -1
  127. package/dist/tests/table_history.test.js +51 -0
  128. package/dist/tests/table_history.test.js.map +1 -1
  129. package/dist/tests/tag.test.js +1 -1
  130. package/dist/tests/tag.test.js.map +1 -1
  131. package/dist/tests/view.test.js +16 -2
  132. package/dist/tests/view.test.js.map +1 -1
  133. package/dist/utils.d.ts +2 -17
  134. package/dist/utils.d.ts.map +1 -1
  135. package/dist/utils.js +19 -32
  136. package/dist/utils.js.map +1 -1
  137. package/package.json +8 -7
@@ -20,10 +20,10 @@ const { text, text_attr, script, domReady, div, button, i, pre, } = require("@sa
20
20
  const { renderForm } = require("@saltcorn/markup");
21
21
  const FieldRepeat = require("../../models/fieldrepeat");
22
22
  const { get_expression_function, expressionChecker, eval_expression, freeVariables, freeVariablesInInterpolation, } = require("../../models/expression");
23
- const { InvalidConfiguration, isNode, isOfflineMode, mergeIntoWhere, dollarizeObject, getSessionId, } = require("../../utils");
23
+ const { InvalidConfiguration, isNode, isOfflineMode, mergeIntoWhere, dollarizeObject, getSessionId, interpolate, } = require("../../utils");
24
24
  const Library = require("../../models/library");
25
25
  const { check_view_columns } = require("../../plugin-testing");
26
- const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, getActionConfigFields, run_action_column, add_free_variables_to_joinfields, readState, stateToQueryString, pathToState, relationTypeFromPath, } = require("../../plugin-helper");
26
+ const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, getActionConfigFields, run_action_column, add_free_variables_to_joinfields, readState, stateToQueryString, pathToState, displayType, } = require("../../plugin-helper");
27
27
  const { splitUniques, getForm, fill_presets, parse_view_select, get_view_link_query, objToQueryString, action_url, action_link, view_linker, } = require("./viewable_fields");
28
28
  const { traverse, getStringsForI18n, translateLayout, traverseSync, } = require("../../models/layout");
29
29
  const { asyncMap, isWeb, removeEmptyStrings } = require("../../utils");
@@ -31,6 +31,7 @@ const { extractFromLayout } = require("../../diagram/node_extract_utils");
31
31
  const db = require("../../db");
32
32
  const { prepare_update_row } = require("../../web-mobile-commons");
33
33
  const _ = require("underscore");
34
+ const { Relation, RelationType } = require("@saltcorn/common-code");
34
35
  const builtInActions = [
35
36
  "Save",
36
37
  "SaveAndContinue",
@@ -73,6 +74,7 @@ const configuration_workflow = (req) => new Workflow({
73
74
  const triggerActions = [];
74
75
  (await Trigger.find({
75
76
  when_trigger: { or: ["API call", "Never"] },
77
+ table_id: null,
76
78
  })).forEach((tr) => {
77
79
  actions.push(tr.name);
78
80
  triggerActions.push(tr.name);
@@ -141,7 +143,7 @@ const configuration_workflow = (req) => new Workflow({
141
143
  field_view_options.remember = ["edit"];
142
144
  }
143
145
  const library = (await Library.find({})).filter((l) => l.suitableFor("edit"));
144
- const myviewrow = await View.findOne({ name: context.viewname });
146
+ const myviewrow = View.findOne({ name: context.viewname });
145
147
  const { parent_field_list } = await table.get_parent_relations(true, true);
146
148
  const pages = await Page.find();
147
149
  const groups = (await PageGroup.find()).map((g) => ({
@@ -376,12 +378,6 @@ const setDateLocales = (form, locale) => {
376
378
  }
377
379
  });
378
380
  };
379
- /**
380
- * check if a relation path has a CHildList structure
381
- */
382
- const isChildListPath = (viewSelect, subView) => viewSelect.type === "RelationPath" &&
383
- relationTypeFromPath(subView, viewSelect.path, viewSelect.sourcetable) ===
384
- "ChildList";
385
381
  /**
386
382
  * update viewSelect so that it looks like a normal ChildList
387
383
  */
@@ -469,7 +465,18 @@ const runMany = async (table_id, viewname, { columns, layout, auto_save, split_p
469
465
  * @returns {Promise<void>}
470
466
  */
471
467
  const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname, optionsQuery, }) => {
468
+ let pseudo_row = {};
469
+ if (!row) {
470
+ table.fields.forEach((f) => {
471
+ pseudo_row[f.name] = undefined;
472
+ });
473
+ }
472
474
  await traverse(form.layout, {
475
+ container(segment) {
476
+ if (segment.click_action) {
477
+ segment.url = `javascript:view_post('${viewname}', 'run_action', {click_action: '${segment.click_action}', ...get_form_record({viewname: '${viewname}'}) })`;
478
+ }
479
+ },
473
480
  async action(segment) {
474
481
  if (segment.action_style === "on_page_load") {
475
482
  //TODO check segment.min_role
@@ -481,11 +488,11 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
481
488
  try {
482
489
  const actionResult = await run_action_column({
483
490
  col: { ...segment },
484
- referrer: req.get("Referrer"),
491
+ referrer: req?.get?.("Referrer"),
485
492
  req,
486
493
  res,
487
494
  table,
488
- row,
495
+ row: row || pseudo_row,
489
496
  });
490
497
  segment.type = "blank";
491
498
  segment.style = {};
@@ -527,25 +534,19 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
527
534
  segment.sourceURL = `/field/show-calculated/${table.name}/${segment.join_field}/${segment.fieldview}?${qs}`;
528
535
  },
529
536
  tabs(segment) {
537
+ const to_delete = new Set();
530
538
  (segment.showif || []).forEach((sif, ix) => {
531
539
  if (sif) {
532
- const showit = eval_expression(sif, row || {}, req.user);
533
- if (!showit) {
534
- segment.titles.splice(ix, 1);
535
- segment.contents.splice(ix, 1);
536
- }
540
+ const showit = eval_expression(sif, row || pseudo_row, req.user);
541
+ if (!showit)
542
+ to_delete.add(ix);
537
543
  }
538
544
  });
545
+ segment.titles = segment.titles.filter((v, ix) => !to_delete.has(ix));
546
+ segment.contents = segment.contents.filter((v, ix) => !to_delete.has(ix));
539
547
  (segment.titles || []).forEach((t, ix) => {
540
548
  if (typeof t === "string" && t.includes("{{")) {
541
- const template = _.template(t, {
542
- interpolate: /\{\{([^#].+?)\}\}/g,
543
- });
544
- segment.titles[ix] = template({
545
- user: req.user,
546
- row,
547
- ...(row || {}),
548
- });
549
+ segment.titles[ix] = interpolate(t, row, req.user);
549
550
  }
550
551
  });
551
552
  },
@@ -568,11 +569,17 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
568
569
  const view = View.findOne({ name: view_select.viewname });
569
570
  if (!view)
570
571
  throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);
571
- const childListPath = isChildListPath(view_select, view);
572
+ // check if the relation path matches a ChildList relations
573
+ let childListRelPath = false;
574
+ if (segment.relation && view.table_id) {
575
+ const targetTbl = Table.findOne({ id: view.table_id });
576
+ const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
577
+ childListRelPath = relation.type === RelationType.CHILD_LIST;
578
+ }
572
579
  // Edit-in-edit
573
580
  if (view.viewtemplate === "Edit" &&
574
- (view_select.type === "ChildList" || childListPath)) {
575
- if (childListPath)
581
+ (view_select.type === "ChildList" || childListRelPath)) {
582
+ if (childListRelPath)
576
583
  updateViewSelect(view_select);
577
584
  const childTable = Table.findOne({ id: view.table_id });
578
585
  const childForm = await getForm(childTable, view.name, view.configuration.columns, view.configuration.layout, row?.id, req, !isWeb(req));
@@ -619,42 +626,51 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
619
626
  segment.field_repeat = fr;
620
627
  return;
621
628
  }
622
- const isIndependent = view_select.type === "Independent" ||
623
- (view_select.type === "RelationPath" &&
624
- relationTypeFromPath(view, view_select.path, table) ===
625
- "Independent");
626
- if (!row && !isIndependent) {
627
- segment.type = "blank";
628
- segment.contents = "";
629
- return;
629
+ let state = {};
630
+ if (view_select.type === "RelationPath" && view.table_id) {
631
+ const targetTbl = Table.findOne({ id: view.table_id });
632
+ if (targetTbl) {
633
+ const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
634
+ const type = relation.type;
635
+ if (!row && type !== RelationType.INDEPENDENT) {
636
+ segment.type = "blank";
637
+ segment.contents = "";
638
+ return;
639
+ }
640
+ state = pathToState(relation, (k) => row[k]);
641
+ }
630
642
  }
631
- if (!view)
632
- throw new InvalidConfiguration(`Edit view incorrectly configured: cannot find embedded view ${view_select.viewname}`);
633
- let state;
634
- switch (view_select.type) {
635
- case "RelationPath": {
636
- state = pathToState(view, segment.relation, view_select.path, (k) => row[k], table);
637
- break;
643
+ else {
644
+ const isIndependent = view_select.type === "Independent";
645
+ // legacy none check ?
646
+ if (!row && !isIndependent) {
647
+ segment.type = "blank";
648
+ segment.contents = "";
649
+ return;
650
+ }
651
+ if (!view)
652
+ throw new InvalidConfiguration(`Edit view incorrectly configured: cannot find embedded view ${view_select.viewname}`);
653
+ switch (view_select.type) {
654
+ case "Own":
655
+ state = { id: row.id };
656
+ break;
657
+ case "Independent":
658
+ state = {};
659
+ break;
660
+ case "ChildList":
661
+ case "OneToOneShow":
662
+ state = { [view_select.field_name]: row.id };
663
+ break;
664
+ case "ParentShow":
665
+ state = { id: row[view_select.field_name] };
666
+ break;
638
667
  }
639
- case "Own":
640
- state = { id: row.id };
641
- break;
642
- case "Independent":
643
- state = {};
644
- break;
645
- case "ChildList":
646
- case "OneToOneShow":
647
- state = { [view_select.field_name]: row.id };
648
- break;
649
- case "ParentShow":
650
- state = { id: row[view_select.field_name] };
651
- break;
652
668
  }
653
669
  const extra_state = segment.extra_state_fml
654
670
  ? eval_expression(segment.extra_state_fml, {
655
671
  ...dollarizeObject(req.query),
656
672
  session_id: getSessionId(req),
657
- ...(row || {}),
673
+ ...(row || pseudo_row),
658
674
  }, req.user)
659
675
  : {};
660
676
  const qs = stateToQueryString({ ...state, ...extra_state });
@@ -818,6 +834,8 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
818
834
  fixed,
819
835
  auto_save,
820
836
  }, { req, res }, body, { getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery }, remote);
837
+ const view = View.findOne({ name: viewname });
838
+ const pagetitle = { title: viewname, no_menu: view?.attributes?.no_menu };
821
839
  if (prepResult) {
822
840
  let { form, row, pk, id } = prepResult;
823
841
  const cancel = body._cancel;
@@ -850,7 +868,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
850
868
  }
851
869
  else {
852
870
  req.flash("error", text_attr(ins_upd_error));
853
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
871
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
854
872
  }
855
873
  return;
856
874
  }
@@ -858,8 +876,15 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
858
876
  for (const field of form.fields.filter((f) => f.isRepeat)) {
859
877
  const view_select = parse_view_select(field.metadata.view, field.metadata.relation_path);
860
878
  const childView = View.findOne({ name: view_select.viewname });
861
- if (isChildListPath(view_select, childView))
862
- updateViewSelect(view_select);
879
+ if (!childView)
880
+ throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);
881
+ if (field.metadata.relation_path &&
882
+ view_select.type === "RelationPath") {
883
+ const targetTbl = Table.findOne({ id: childView.table_id });
884
+ const relation = new Relation(field.metadata.relation_path, targetTbl.name, displayType(await childView.get_state_fields()));
885
+ if (relation.type === RelationType.CHILD_LIST)
886
+ updateViewSelect(view_select);
887
+ }
863
888
  const childTable = Table.findOne({ id: field.metadata?.table_id });
864
889
  const submitted_row_ids = new Set((form.values[field.name] || []).map((srow) => `${srow[childTable.pk_name]}`));
865
890
  const childFields = new Set(childTable.fields.map((f) => f.name));
@@ -876,7 +901,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
876
901
  const upd_res = await childTable.tryUpdateRow(childRow, childRow[childTable.pk_name], req.user || { role_id: 100 });
877
902
  if (upd_res.error) {
878
903
  req.flash("error", text_attr(upd_res.error));
879
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
904
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
880
905
  return;
881
906
  }
882
907
  }
@@ -884,7 +909,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
884
909
  const ins_res = await childTable.tryInsertRow(childRow, req.user || { role_id: 100 });
885
910
  if (ins_res.error) {
886
911
  req.flash("error", text_attr(ins_res.error));
887
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
912
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
888
913
  return;
889
914
  }
890
915
  else if (ins_res.success) {
@@ -1025,6 +1050,56 @@ const doAuthPost = async ({ body, table_id, req }) => {
1025
1050
  const authorise_post = async ({ body, table_id, req }, { authorizePostQuery }) => {
1026
1051
  return await authorizePostQuery(body, table_id);
1027
1052
  };
1053
+ const openDataStream = async (tableId, viewName, id, fieldName, fieldView, user, configuration, targetOpts) => {
1054
+ const table = Table.findOne({ id: tableId });
1055
+ const field = table.getField(fieldName);
1056
+ if (!field)
1057
+ throw new InvalidConfiguration(`Field ${fieldName} not found`);
1058
+ if (field.type === "File") {
1059
+ const cfgCol = configuration.columns.find((col) => col.fieldview === fieldView && col.field_name === fieldName);
1060
+ const fileView = getState().fileviews[fieldView];
1061
+ if (!fileView)
1062
+ throw new InvalidConfiguration(`File view ${fieldView} not found`);
1063
+ return await fileView.openDataStream(tableId, id, fieldName, user, cfgCol.configuration, targetOpts);
1064
+ }
1065
+ };
1066
+ // TODO is owner check
1067
+ const authorizeDataStream = async (view, id, fieldName, user, targetOpts) => {
1068
+ if (!user || user.role_id > view.min_role)
1069
+ return false;
1070
+ else {
1071
+ const table = Table.findOne({ id: view.table_id });
1072
+ if (!table || user.role_id > table.min_role_write)
1073
+ return false;
1074
+ else {
1075
+ const field = table.getField(fieldName);
1076
+ if (field.type === "File") {
1077
+ if (targetOpts?.oldTarget) {
1078
+ // continue old file ?
1079
+ const file = await File.findOne(targetOpts.oldTarget);
1080
+ if (file)
1081
+ return file.min_role_read >= user.role_id;
1082
+ }
1083
+ else if (id) {
1084
+ // continue file of existing row ?
1085
+ const row = await table.getRow({ [table.pk_name]: id });
1086
+ const fileCol = row[fieldName];
1087
+ if (fileCol) {
1088
+ const file = await File.findOne(row[fieldName]);
1089
+ if (file)
1090
+ return file.min_role_read >= user.role_id;
1091
+ }
1092
+ }
1093
+ // stream is new or the file does not exist
1094
+ return true;
1095
+ }
1096
+ else {
1097
+ // only files for now
1098
+ return false;
1099
+ }
1100
+ }
1101
+ }
1102
+ };
1028
1103
  /**
1029
1104
  * @param {number} table_id
1030
1105
  * @param {*} viewname
@@ -1135,7 +1210,8 @@ const prepare = async (viewname, table, fields, { columns, layout, fixed, auto_s
1135
1210
  if (req.xhr)
1136
1211
  res.status(422);
1137
1212
  await form.fill_fkey_options(false, optionsQuery, req.user);
1138
- res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
1213
+ const view = View.findOne({ name: viewname });
1214
+ res.sendWrap({ title: viewname, no_menu: view?.attributes?.no_menu }, renderForm(form, req.csrfToken ? req.csrfToken() : false));
1139
1215
  return null;
1140
1216
  }
1141
1217
  let row;
@@ -1158,7 +1234,7 @@ const prepare = async (viewname, table, fields, { columns, layout, fixed, auto_s
1158
1234
  }
1159
1235
  const file_fields = form.fields.filter((f) => f.type === "File");
1160
1236
  for (const field of file_fields) {
1161
- if (!field.fieldviewObj?.isEdit)
1237
+ if (!field.fieldviewObj?.isEdit || field.fieldviewObj?.isStream)
1162
1238
  continue;
1163
1239
  if (field.fieldviewObj?.setsFileId) {
1164
1240
  //do nothing
@@ -1327,6 +1403,8 @@ module.exports = {
1327
1403
  run,
1328
1404
  runMany,
1329
1405
  runPost,
1406
+ openDataStream,
1407
+ authorizeDataStream,
1330
1408
  get_state_fields,
1331
1409
  initial_config,
1332
1410
  /** @type {boolean} */
@@ -1349,7 +1427,7 @@ module.exports = {
1349
1427
  getStringsForI18n({ layout }) {
1350
1428
  return getStringsForI18n(layout);
1351
1429
  },
1352
- queries: ({ table_id, name, configuration: { columns, default_state, layout, auto_save, split_paste, destination_type, }, req, res, }) => ({
1430
+ queries: ({ table_id, name, configuration: { columns, default_state, layout, auto_save, split_paste, destination_type, fixed, }, req, res, }) => ({
1353
1431
  async editQuery(state, mobileReferrer) {
1354
1432
  const table = Table.findOne({ id: table_id });
1355
1433
  const fields = table.getFields();
@@ -1497,17 +1575,46 @@ module.exports = {
1497
1575
  });
1498
1576
  },
1499
1577
  async actionQuery() {
1500
- const { rndid, _csrf, onchange_action, onchange_field, ...body } = req.body;
1578
+ const { rndid, _csrf, onchange_action, onchange_field, click_action, ...body } = req.body;
1501
1579
  const table = Table.findOne({ id: table_id });
1502
- const dbrow = body.id
1580
+ let row = body.id
1503
1581
  ? await table.getRow({ id: body.id }, {
1504
1582
  forPublic: !req.user,
1505
1583
  forUser: req.user,
1506
1584
  })
1507
- : undefined;
1508
- const row = { ...dbrow, ...body };
1585
+ : {};
1586
+ table.fields.forEach((f) => {
1587
+ if (!f?.validate)
1588
+ return;
1589
+ const valres = f.validate(body);
1590
+ if ("success" in valres)
1591
+ row[f.name] = valres.success;
1592
+ });
1593
+ const use_fixed = await fill_presets(table, req, fixed);
1594
+ row = { ...use_fixed, ...row };
1509
1595
  try {
1510
- if (onchange_action && !rndid) {
1596
+ if (click_action) {
1597
+ let container;
1598
+ traverseSync(layout, {
1599
+ container(segment) {
1600
+ if (segment.click_action === click_action)
1601
+ container = segment;
1602
+ },
1603
+ });
1604
+ if (!container)
1605
+ return { json: { error: "Action not found" } };
1606
+ const trigger = Trigger.findOne({ name: click_action });
1607
+ const result = await trigger.runWithoutRow({
1608
+ table,
1609
+ Table,
1610
+ req,
1611
+ row,
1612
+ referrer: req?.get?.("Referrer"),
1613
+ user: req.user,
1614
+ });
1615
+ return { json: { success: "ok", ...(result || {}) } };
1616
+ }
1617
+ else if (onchange_action && !rndid) {
1511
1618
  const fldCol = columns.find((c) => c.field_name === onchange_field &&
1512
1619
  c.onchange_action === onchange_action);
1513
1620
  if (!fldCol)
@@ -1518,6 +1625,7 @@ module.exports = {
1518
1625
  Table,
1519
1626
  req,
1520
1627
  row,
1628
+ referrer: req?.get?.("Referrer"),
1521
1629
  user: req.user,
1522
1630
  });
1523
1631
  return { json: { success: "ok", ...(result || {}) } };
@@ -1530,7 +1638,7 @@ module.exports = {
1530
1638
  table,
1531
1639
  row,
1532
1640
  res,
1533
- referrer: req.get("Referrer"),
1641
+ referrer: req?.get?.("Referrer"),
1534
1642
  });
1535
1643
  //console.log("result", result);
1536
1644
  return { json: { success: "ok", ...(result || {}) } };
@@ -1601,18 +1709,10 @@ module.exports = {
1601
1709
  where: { [tbl.pk_name]: state[tbl.pk_name] },
1602
1710
  joinFields,
1603
1711
  });
1604
- const template = _.template(title, {
1605
- interpolate: /\{\{([^#].+?)\}\}/g,
1606
- });
1607
- let t = template({ row, ...row });
1608
- return t;
1712
+ return interpolate(title, row);
1609
1713
  }
1610
1714
  else {
1611
- const template = _.template(title, {
1612
- interpolate: /\{\{([^#].+?)\}\}/g,
1613
- });
1614
- let t = template({ row: null });
1615
- return t;
1715
+ return interpolate(title, null);
1616
1716
  }
1617
1717
  },
1618
1718
  configCheck: async (view) => {
@@ -1620,7 +1720,7 @@ module.exports = {
1620
1720
  const errs = [];
1621
1721
  const warnings = [];
1622
1722
  if (!destination_type || destination_type === "View") {
1623
- const vwd = await View.findOne({
1723
+ const vwd = View.findOne({
1624
1724
  name: (view_when_done || "").split(".")[0],
1625
1725
  });
1626
1726
  if (!vwd)