@saltcorn/data 0.8.8-beta.3 → 0.8.8-beta.5

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 (82) hide show
  1. package/dist/base-plugin/actions.d.ts.map +1 -1
  2. package/dist/base-plugin/actions.js +9 -3
  3. package/dist/base-plugin/actions.js.map +1 -1
  4. package/dist/base-plugin/fieldviews.d.ts +30 -16
  5. package/dist/base-plugin/fieldviews.d.ts.map +1 -1
  6. package/dist/base-plugin/fieldviews.js +171 -1
  7. package/dist/base-plugin/fieldviews.js.map +1 -1
  8. package/dist/base-plugin/index.d.ts +98 -62
  9. package/dist/base-plugin/index.d.ts.map +1 -1
  10. package/dist/base-plugin/types.d.ts.map +1 -1
  11. package/dist/base-plugin/types.js +6 -0
  12. package/dist/base-plugin/types.js.map +1 -1
  13. package/dist/base-plugin/viewtemplates/edit.d.ts +39 -2
  14. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  15. package/dist/base-plugin/viewtemplates/edit.js +320 -147
  16. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  17. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  18. package/dist/base-plugin/viewtemplates/filter.js +19 -10
  19. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  20. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  21. package/dist/base-plugin/viewtemplates/list.js +20 -13
  22. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  23. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  24. package/dist/base-plugin/viewtemplates/show.js +14 -3
  25. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  26. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  27. package/dist/base-plugin/viewtemplates/viewable_fields.js +7 -5
  28. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  29. package/dist/db/fixtures.d.ts.map +1 -1
  30. package/dist/db/fixtures.js +76 -0
  31. package/dist/db/fixtures.js.map +1 -1
  32. package/dist/models/config.d.ts.map +1 -1
  33. package/dist/models/config.js +2 -2
  34. package/dist/models/config.js.map +1 -1
  35. package/dist/models/expression.d.ts.map +1 -1
  36. package/dist/models/expression.js +10 -8
  37. package/dist/models/expression.js.map +1 -1
  38. package/dist/models/field.d.ts +1 -1
  39. package/dist/models/field.d.ts.map +1 -1
  40. package/dist/models/field.js +6 -2
  41. package/dist/models/field.js.map +1 -1
  42. package/dist/models/form.d.ts +2 -0
  43. package/dist/models/form.d.ts.map +1 -1
  44. package/dist/models/form.js +1 -0
  45. package/dist/models/form.js.map +1 -1
  46. package/dist/models/index.d.ts +1 -1
  47. package/dist/models/table.d.ts +1 -0
  48. package/dist/models/table.d.ts.map +1 -1
  49. package/dist/models/table.js +43 -30
  50. package/dist/models/table.js.map +1 -1
  51. package/dist/models/table_constraints.d.ts.map +1 -1
  52. package/dist/models/table_constraints.js +2 -1
  53. package/dist/models/table_constraints.js.map +1 -1
  54. package/dist/models/trigger.d.ts.map +1 -1
  55. package/dist/models/trigger.js +23 -11
  56. package/dist/models/trigger.js.map +1 -1
  57. package/dist/models/user.d.ts +2 -0
  58. package/dist/models/user.d.ts.map +1 -1
  59. package/dist/models/user.js +10 -2
  60. package/dist/models/user.js.map +1 -1
  61. package/dist/models/view.d.ts.map +1 -1
  62. package/dist/models/view.js +25 -8
  63. package/dist/models/view.js.map +1 -1
  64. package/dist/plugin-helper.d.ts.map +1 -1
  65. package/dist/plugin-helper.js +7 -3
  66. package/dist/plugin-helper.js.map +1 -1
  67. package/dist/tests/auth.test.js +13 -0
  68. package/dist/tests/auth.test.js.map +1 -1
  69. package/dist/tests/auxtest.test.js +2 -1
  70. package/dist/tests/auxtest.test.js.map +1 -1
  71. package/dist/tests/calc.test.js +16 -1
  72. package/dist/tests/calc.test.js.map +1 -1
  73. package/dist/tests/table.test.js +11 -1
  74. package/dist/tests/table.test.js.map +1 -1
  75. package/dist/tests/user.test.js +5 -0
  76. package/dist/tests/user.test.js.map +1 -1
  77. package/dist/tests/view.test.js +1 -1
  78. package/dist/utils.d.ts +1 -0
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +11 -1
  81. package/dist/utils.js.map +1 -1
  82. package/package.json +7 -7
@@ -22,15 +22,17 @@ const { get_expression_function, expressionChecker, eval_expression, freeVariabl
22
22
  const { InvalidConfiguration, isNode, mergeIntoWhere } = require("../../utils");
23
23
  const Library = require("../../models/library");
24
24
  const { check_view_columns } = require("../../plugin-testing");
25
- const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, get_link_view_opts, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, strictParseInt, run_action_column, add_free_variables_to_joinfields, } = require("../../plugin-helper");
25
+ const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, get_link_view_opts, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, run_action_column, add_free_variables_to_joinfields, readState, } = require("../../plugin-helper");
26
26
  const { splitUniques, getForm, fill_presets, parse_view_select, get_view_link_query, objToQueryString, action_url, action_link, view_linker, } = require("./viewable_fields");
27
27
  const { traverse, getStringsForI18n, translateLayout, traverseSync, } = require("../../models/layout");
28
- const { asyncMap, isWeb } = require("../../utils");
28
+ const { asyncMap, isWeb, removeEmptyStrings } = require("../../utils");
29
29
  const { extractFromLayout } = require("../../diagram/node_extract_utils");
30
30
  const db = require("../../db");
31
+ const { prepare_update_row } = require("../../web-mobile-commons");
31
32
  const builtInActions = [
32
33
  "Save",
33
34
  "SaveAndContinue",
35
+ "UpdateMatchingRows",
34
36
  "Reset",
35
37
  "GoBack",
36
38
  "Delete",
@@ -678,89 +680,18 @@ const render = async ({ table, fields, viewname, columns, layout, row, req, stat
678
680
  * @param {string} optsTwo.redirect
679
681
  * @returns {Promise<void>}
680
682
  */
681
- const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_done, formula_destinations, auto_save, destination_type, dest_url_formula, page_when_done, }, state, body, { res, req, redirect }, { tryInsertQuery, tryUpdateQuery, getRowQuery, saveFileQuery, optionsQuery }, remote) => {
683
+ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_done, formula_destinations, auto_save, destination_type, dest_url_formula, page_when_done, }, state, body, { res, req, redirect }, { tryInsertQuery, tryUpdateQuery, getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery, }, remote) => {
682
684
  const table = Table.findOne({ id: table_id });
683
685
  const fields = table.getFields();
684
- const form = await getForm(table, viewname, columns, layout, body.id, req);
685
- if (auto_save)
686
- form.onChange = `saveAndContinue(this, ${!isWeb(req) ? `'${form.action}'` : undefined})`;
687
- Object.entries(body).forEach(([k, v]) => {
688
- const form_field = form.fields.find((f) => f.name === k);
689
- const tbl_field = fields.find((f) => f.name === k);
690
- if (tbl_field && !form_field && !fixed?.[`_block_${k}`]) {
691
- form.fields.push(new Field({ name: k, input_type: "hidden" }));
692
- }
693
- });
694
- setDateLocales(form, req.getLocale());
695
- await transformForm({
696
- form,
697
- table,
698
- req,
699
- row: body[table.pk_name]
700
- ? { [table.pk_name]: body[table.pk_name] }
701
- : undefined,
702
- getRowQuery,
703
- viewname,
704
- optionsQuery,
705
- });
706
- const cancel = body._cancel;
707
- await form.asyncValidate(body);
708
- if (form.hasErrors && !cancel) {
709
- if (req.xhr)
710
- res.status(422);
711
- await form.fill_fkey_options(false, undefined, req.user);
712
- res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
713
- }
714
- else {
715
- let row;
716
- const pk = fields.find((f) => f.primary_key);
717
- let id = pk.type.read(body[pk.name]);
718
- if (typeof id === "undefined") {
719
- const use_fixed = await fill_presets(table, req, fixed);
720
- row = { ...use_fixed, ...form.values };
721
- }
722
- else if (cancel) {
723
- //get row
724
- row = await table.getRow({ id });
725
- }
726
- else {
727
- row = { ...form.values };
728
- }
729
- for (const field of form.fields.filter((f) => f.isRepeat)) {
730
- delete row[field.name];
731
- }
732
- const file_fields = form.fields.filter((f) => f.type === "File");
733
- for (const field of file_fields) {
734
- if (field.fieldviewObj?.setsFileId) {
735
- //do nothing
736
- }
737
- else if (field.fieldviewObj?.setsDataURL) {
738
- if (body[field.name]) {
739
- if (body[field.name].startsWith("data:")) {
740
- const path_to_serve = await saveFileQuery(body[field.name], field.id, field.fieldview, row);
741
- row[field.name] = path_to_serve;
742
- }
743
- }
744
- }
745
- else if (req.files && req.files[field.name]) {
746
- if (!isNode() && !remote) {
747
- req.flash("error", "The mobile-app supports no local files, please use a remote table.");
748
- res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
749
- return;
750
- }
751
- if (isNode()) {
752
- const file = await File.from_req_files(req.files[field.name], req.user ? req.user.id : null, (field.attributes && +field.attributes.min_role_read) || 1, field?.attributes?.folder);
753
- row[field.name] = file.path_to_serve;
754
- }
755
- else {
756
- const serverResp = await File.upload(req.files[field.name]);
757
- row[field.name] = serverResp.location;
758
- }
759
- }
760
- else {
761
- delete row[field.name];
762
- }
763
- }
686
+ const prepResult = await prepare(viewname, table, fields, {
687
+ columns,
688
+ layout,
689
+ fixed,
690
+ auto_save,
691
+ }, { req, res }, body, { getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery }, remote);
692
+ if (prepResult) {
693
+ let { form, row, pk, id } = prepResult;
694
+ const cancel = body._cancel;
764
695
  const originalID = id;
765
696
  let trigger_return;
766
697
  let ins_upd_error;
@@ -859,64 +790,14 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
859
790
  res.json({ view_when_done, ...trigger_return });
860
791
  return;
861
792
  }
862
- if (redirect) {
863
- res.redirect(redirect);
864
- return;
865
- }
866
- let use_view_when_done = view_when_done;
867
- if (destination_type === "Back to referer" && body._referer) {
868
- res.redirect(body._referer);
869
- return;
870
- }
871
- else if (destination_type === "Page" && page_when_done) {
872
- res.redirect(`/page/${page_when_done}`);
873
- return;
874
- }
875
- else if (destination_type === "URL formula" && dest_url_formula) {
876
- const url = eval_expression(dest_url_formula, row);
877
- res.redirect(url);
878
- return;
879
- }
880
- else if (destination_type !== "View")
881
- for (const { view, expression } of formula_destinations || []) {
882
- if (expression) {
883
- const f = get_expression_function(expression, fields);
884
- if (f(row)) {
885
- use_view_when_done = view;
886
- continue;
887
- }
888
- }
889
- }
890
- if (!use_view_when_done) {
891
- res.redirect(`/`);
892
- return;
893
- }
894
- const [viewname_when_done, relation] = use_view_when_done.split(".");
895
- const nxview = await View.findOne({ name: viewname_when_done });
896
- if (!nxview) {
897
- req.flash("warning", `View "${use_view_when_done}" not found - change "View when done" in "${viewname}" view`);
898
- res.redirect(`/`);
899
- }
900
- else {
901
- const state_fields = await nxview.get_state_fields();
902
- let target = `/view/${text(viewname_when_done)}`;
903
- let query = "";
904
- if ((nxview.table_id === table_id || relation) &&
905
- state_fields.some((sf) => sf.name === pk.name) &&
906
- viewname_when_done !== viewname) {
907
- const get_query = get_view_link_query(fields, nxview);
908
- query = relation
909
- ? `?${pk.name}=${text(row[relation])}`
910
- : get_query(row);
911
- }
912
- const redirectPath = `${target}${query}`;
913
- if (!isWeb(req)) {
914
- res.json({ redirect: `get${redirectPath}` });
915
- }
916
- else {
917
- res.redirect(redirectPath);
918
- }
919
- }
793
+ await whenDone(viewname, table_id, fields, pk, {
794
+ view_when_done,
795
+ formula_destinations,
796
+ destination_type,
797
+ dest_url_formula,
798
+ page_when_done,
799
+ redirect,
800
+ }, req, res, body, row);
920
801
  }
921
802
  };
922
803
  const doAuthPost = async ({ body, table_id, req }) => {
@@ -1015,6 +896,253 @@ const run_action = async (table_id, viewname, { columns, layout }, body, { req,
1015
896
  }
1016
897
  return result;
1017
898
  };
899
+ const update_matching_rows = async (table_id, viewname, { columns, layout, fixed, view_when_done, formula_destinations, auto_save, destination_type, dest_url_formula, page_when_done, }, body, { req, res, redirect }, { updateMatchingQuery, getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery, }) => {
900
+ const table = Table.findOne({ id: table_id });
901
+ const fields = table.getFields();
902
+ const prepResult = await prepare(viewname, table, fields, {
903
+ columns,
904
+ layout,
905
+ fixed,
906
+ auto_save,
907
+ }, { req, res }, body, { getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery });
908
+ if (prepResult) {
909
+ let { form, row, pk } = prepResult;
910
+ const state = req?.query
911
+ ? readState(removeEmptyStrings(req.query), fields, req)
912
+ : {};
913
+ const where = stateFieldsToWhere({ fields, state, table });
914
+ const repeatFields = form.fields.filter((f) => f.isRepeat);
915
+ const childRows = {};
916
+ for (const field of repeatFields)
917
+ childRows[field.name] = form.values[field.name];
918
+ const { id, ...rest } = row;
919
+ const uptResults = await updateMatchingQuery(where, rest, repeatFields, childRows);
920
+ if (uptResults.error || uptResults.rowError || uptResults.inEditError) {
921
+ res.status(422);
922
+ req.flash("error", text_attr(uptResults.error || uptResults.rowError || uptResults.inEditError));
923
+ res.sendWrap(viewname, renderForm(form, req.csrfToken()));
924
+ return;
925
+ }
926
+ const { success, danger, goto } = combineResults(uptResults);
927
+ if (success.length > 0) {
928
+ req.flash("success", success);
929
+ }
930
+ if (danger.length > 0) {
931
+ req.flash("danger", danger);
932
+ }
933
+ else if (goto) {
934
+ res.redirect(goto);
935
+ return;
936
+ }
937
+ await whenDone(viewname, table_id, fields, pk, {
938
+ view_when_done,
939
+ formula_destinations,
940
+ destination_type,
941
+ dest_url_formula,
942
+ page_when_done,
943
+ redirect,
944
+ }, req, res, body, row);
945
+ }
946
+ };
947
+ /**
948
+ * preparations for the form and the data row
949
+ * @param {*} viewname
950
+ * @param {*} table table of the view
951
+ * @param {*} fields all fields in table
952
+ * @param {*} param3 columns, layout, fixed, auto_save
953
+ * @param {*} param4 req, res
954
+ * @param {*} body request body
955
+ * @param {*} param6 getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery
956
+ * @param {*} remote
957
+ * @returns null on error, { form, row, pk, id } on success
958
+ */
959
+ const prepare = async (viewname, table, fields, { columns, layout, fixed, auto_save }, { req, res }, body, { getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery }, remote) => {
960
+ const form = await getForm(table, viewname, columns, layout, body.id, req);
961
+ if (auto_save)
962
+ form.onChange = `saveAndContinue(this, ${!isWeb(req) ? `'${form.action}'` : undefined})`;
963
+ Object.entries(body).forEach(([k, v]) => {
964
+ const form_field = form.fields.find((f) => f.name === k);
965
+ const tbl_field = fields.find((f) => f.name === k);
966
+ if (tbl_field && !form_field && !fixed?.[`_block_${k}`]) {
967
+ form.fields.push(new Field({ name: k, input_type: "hidden" }));
968
+ }
969
+ });
970
+ setDateLocales(form, req.getLocale());
971
+ await transformForm({
972
+ form,
973
+ table,
974
+ req,
975
+ row: body[table.pk_name]
976
+ ? { [table.pk_name]: body[table.pk_name] }
977
+ : undefined,
978
+ getRowQuery,
979
+ viewname,
980
+ optionsQuery,
981
+ });
982
+ const cancel = body._cancel;
983
+ await form.asyncValidate(body);
984
+ if (form.hasErrors && !cancel) {
985
+ if (req.xhr)
986
+ res.status(422);
987
+ await form.fill_fkey_options(false, optionsQuery, req.user);
988
+ res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
989
+ return null;
990
+ }
991
+ let row;
992
+ const pk = fields.find((f) => f.primary_key);
993
+ let id = pk.type.read(body[pk.name]);
994
+ if (typeof id === "undefined") {
995
+ const use_fixed = await fill_presets(table, req, fixed);
996
+ row = { ...use_fixed, ...form.values };
997
+ }
998
+ else if (cancel) {
999
+ row = getRowByIdQuery
1000
+ ? await getRowByIdQuery(id)
1001
+ : await table.getRow({ id });
1002
+ }
1003
+ else {
1004
+ row = { ...form.values };
1005
+ }
1006
+ for (const field of form.fields.filter((f) => f.isRepeat)) {
1007
+ delete row[field.name];
1008
+ }
1009
+ const file_fields = form.fields.filter((f) => f.type === "File");
1010
+ for (const field of file_fields) {
1011
+ if (field.fieldviewObj?.setsFileId) {
1012
+ //do nothing
1013
+ }
1014
+ else if (field.fieldviewObj?.setsDataURL) {
1015
+ if (body[field.name]) {
1016
+ if (body[field.name].startsWith("data:")) {
1017
+ const path_to_serve = await saveFileQuery(body[field.name], field.id, field.fieldview, row);
1018
+ row[field.name] = path_to_serve;
1019
+ }
1020
+ }
1021
+ }
1022
+ else if (req.files && req.files[field.name]) {
1023
+ if (!isNode() && !remote) {
1024
+ req.flash("error", "The mobile-app supports no local files, please use a remote table.");
1025
+ res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
1026
+ return null;
1027
+ }
1028
+ if (isNode()) {
1029
+ const file = await File.from_req_files(req.files[field.name], req.user ? req.user.id : null, (field.attributes && +field.attributes.min_role_read) || 1, field?.attributes?.folder);
1030
+ row[field.name] = file.path_to_serve;
1031
+ }
1032
+ else {
1033
+ const serverResp = await File.upload(req.files[field.name]);
1034
+ row[field.name] = serverResp.location;
1035
+ }
1036
+ }
1037
+ else {
1038
+ delete row[field.name];
1039
+ }
1040
+ }
1041
+ return { form, row, pk, id };
1042
+ };
1043
+ /**
1044
+ * take care of final redirect
1045
+ * @param {*} viewname
1046
+ * @param {*} table_id id of the table of the view
1047
+ * @param {*} fields all fields in table
1048
+ * @param {*} pk private key field
1049
+ * @param {*} param4 view_when_done, formula_destinations, destination_type, dest_url_formula, page_when_done, redirect
1050
+ * @param {*} req
1051
+ * @param {*} res
1052
+ * @param {*} body reuqest body
1053
+ * @param {*} row row of the form
1054
+ * @returns
1055
+ */
1056
+ const whenDone = async (viewname, table_id, fields, pk, { view_when_done, formula_destinations, destination_type, dest_url_formula, page_when_done, redirect, }, req, res, body, row) => {
1057
+ if (redirect) {
1058
+ res.redirect(redirect);
1059
+ return;
1060
+ }
1061
+ let use_view_when_done = view_when_done;
1062
+ if (destination_type === "Back to referer" && body._referer) {
1063
+ res.redirect(body._referer);
1064
+ return;
1065
+ }
1066
+ else if (destination_type === "Page" && page_when_done) {
1067
+ res.redirect(`/page/${page_when_done}`);
1068
+ return;
1069
+ }
1070
+ else if (destination_type === "URL formula" && dest_url_formula) {
1071
+ const url = eval_expression(dest_url_formula, row);
1072
+ res.redirect(url);
1073
+ return;
1074
+ }
1075
+ else if (destination_type !== "View")
1076
+ for (const { view, expression } of formula_destinations || []) {
1077
+ if (expression) {
1078
+ const f = get_expression_function(expression, fields);
1079
+ if (f(row)) {
1080
+ use_view_when_done = view;
1081
+ continue;
1082
+ }
1083
+ }
1084
+ }
1085
+ if (!use_view_when_done) {
1086
+ res.redirect(`/`);
1087
+ return;
1088
+ }
1089
+ const [viewname_when_done, relation] = use_view_when_done.split(".");
1090
+ const nxview = View.findOne({ name: viewname_when_done });
1091
+ if (!nxview) {
1092
+ req.flash("warning", `View "${use_view_when_done}" not found - change "View when done" in "${viewname}" view`);
1093
+ res.redirect(`/`);
1094
+ }
1095
+ else {
1096
+ const state_fields = await nxview.get_state_fields();
1097
+ let target = `/view/${text(viewname_when_done)}`;
1098
+ let query = "";
1099
+ if ((nxview.table_id === table_id || relation) &&
1100
+ state_fields.some((sf) => sf.name === pk.name) &&
1101
+ viewname_when_done !== viewname) {
1102
+ const get_query = get_view_link_query(fields, nxview);
1103
+ query = relation ? `?${pk.name}=${text(row[relation])}` : get_query(row);
1104
+ }
1105
+ const redirectPath = `${target}${query}`;
1106
+ if (!isWeb(req)) {
1107
+ res.json({ redirect: `get${redirectPath}` });
1108
+ }
1109
+ else {
1110
+ res.redirect(redirectPath);
1111
+ }
1112
+ }
1113
+ };
1114
+ /**
1115
+ * @param {*} results results from updateMatchingQuery
1116
+ * @returns success, danger, goto
1117
+ */
1118
+ const combineResults = (results) => {
1119
+ const combined = { success: [], danger: [] };
1120
+ for (const uptResult of results) {
1121
+ const trigger_return = uptResult.trigger_return || {};
1122
+ if (trigger_return.notify && trigger_return.details)
1123
+ combined.success.push(div({ class: "d-inline" }, trigger_return.notify, button({
1124
+ class: "btn btn-sm btn-outline-secondary btn-xs",
1125
+ type: "button",
1126
+ "data-bs-toggle": "collapse",
1127
+ "data-bs-target": "#notifyDetails",
1128
+ "aria-expanded": "false",
1129
+ "aria-controls": "notifyDetails",
1130
+ }, i({ class: "fas fa-plus" })), div({ class: "collapse", id: "notifyDetails" }, pre(trigger_return.details))));
1131
+ else if (trigger_return.notify)
1132
+ combined.success.push(trigger_return.notify);
1133
+ if (trigger_return.error)
1134
+ combined.danger.push(trigger_return.error);
1135
+ if (trigger_return.goto && !combined.goto)
1136
+ combined.trigger_return.goto;
1137
+ }
1138
+ return combined;
1139
+ };
1140
+ const tryUpdateImpl = async (row, id, table, user) => {
1141
+ const result = {};
1142
+ const upd_res = await table.tryUpdateRow(row, id, user || { role_id: 100 }, result);
1143
+ upd_res.trigger_return = result;
1144
+ return upd_res;
1145
+ };
1018
1146
  module.exports = {
1019
1147
  /** @type {string} */
1020
1148
  name: "Edit",
@@ -1111,11 +1239,8 @@ module.exports = {
1111
1239
  return ins_res;
1112
1240
  },
1113
1241
  async tryUpdateQuery(row, id) {
1114
- const table = Table.findOne({ id: table_id });
1115
- const result = {};
1116
- const upd_res = await table.tryUpdateRow(row, id, req.user || { role_id: 100 }, result);
1117
- upd_res.trigger_return = result;
1118
- return upd_res;
1242
+ const table = Table.findOne(table_id);
1243
+ return await tryUpdateImpl(row, id, table, req.user);
1119
1244
  },
1120
1245
  async saveFileQuery(fieldVal, fieldId, fieldView, row) {
1121
1246
  const field = await Field.findOne({ id: fieldId });
@@ -1178,6 +1303,10 @@ module.exports = {
1178
1303
  forUser: req.user,
1179
1304
  });
1180
1305
  },
1306
+ async getRowByIdQuery(id) {
1307
+ const table = Table.findOne({ id: table_id });
1308
+ return await table.getRow({ id });
1309
+ },
1181
1310
  async actionQuery() {
1182
1311
  const body = req.body;
1183
1312
  const col = columns.find((c) => c.type === "Action" && c.rndid === body.rndid && body.rndid);
@@ -1207,8 +1336,52 @@ module.exports = {
1207
1336
  const rows = await db.select(reftable_name, type === "File" ? attributes.select_file_where : where);
1208
1337
  return rows;
1209
1338
  },
1339
+ async updateMatchingQuery(where, updateVals, repeatFields, childRows) {
1340
+ const table = Table.findOne(table_id);
1341
+ const rows = await table.getRows(where);
1342
+ const results = [];
1343
+ let inTransaction = false;
1344
+ try {
1345
+ if (rows.length === 0)
1346
+ return results;
1347
+ await db.begin();
1348
+ inTransaction = true;
1349
+ for (const row of rows) {
1350
+ const uptRes = await tryUpdateImpl(updateVals, row.id, table, req.user);
1351
+ if (uptRes.error) {
1352
+ inTransaction = false;
1353
+ await db.rollback();
1354
+ return { rowError: uptRes.error };
1355
+ }
1356
+ results.push(uptRes);
1357
+ for (const field of repeatFields) {
1358
+ const childTable = Table.findOne({ id: field.metadata?.table_id });
1359
+ await childTable.deleteRows({ [field.metadata?.relation]: row.id });
1360
+ for (const childRow of childRows[field.name]) {
1361
+ childRow[field.metadata?.relation] = row.id;
1362
+ const insRow = { ...childRow };
1363
+ delete insRow[childTable.pk_name];
1364
+ const insRes = await childTable.tryInsertRow(insRow, req.user || { role_id: 100 });
1365
+ if (insRes.error) {
1366
+ inTransaction = false;
1367
+ await db.rollback();
1368
+ return { inEditError: insRes.error };
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ if (inTransaction)
1374
+ await db.commit();
1375
+ }
1376
+ catch (error) {
1377
+ if (inTransaction)
1378
+ await db.rollback();
1379
+ return { error: error.message };
1380
+ }
1381
+ return results;
1382
+ },
1210
1383
  }),
1211
- routes: { run_action },
1384
+ routes: { run_action, update_matching_rows },
1212
1385
  configCheck: async (view) => {
1213
1386
  const { name, configuration: { view_when_done, destination_type, dest_url_formula, formula_destinations, page_when_done, }, } = view;
1214
1387
  const errs = [];