@saltcorn/server 0.9.5-beta.0 → 0.9.5-beta.10

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.
package/auth/admin.js CHANGED
@@ -82,6 +82,13 @@ const getUserFields = async (req) => {
82
82
  await iterForm("new_user_form");
83
83
  //console.log(userFields);
84
84
  for (const f of userFields) {
85
+ if (f.is_fkey && !f.fieldview) {
86
+ f.fieldviewObj = getState().keyFieldviews?.select;
87
+ if (f.fieldviewObj) {
88
+ f.input_type = "fromtype";
89
+ f.fieldview = "select";
90
+ }
91
+ }
85
92
  await f.fill_fkey_options();
86
93
  if (f.name === "email") {
87
94
  f.validator = (s) => {
@@ -357,6 +364,7 @@ const auth_settings_form = async (req) =>
357
364
  "user_settings_form",
358
365
  "verification_view",
359
366
  "logout_url",
367
+ "signup_role",
360
368
  "elevate_verified",
361
369
  "email_mask",
362
370
  ],
package/auth/routes.js CHANGED
@@ -514,9 +514,13 @@ router.get(
514
514
  if (!signup_form) await defaultSignup();
515
515
  else {
516
516
  const resp = await signup_form.run_possibly_on_page({}, req, res);
517
- if (signup_form.default_render_page)
518
- res.sendWrap(req.__(`Sign up`), resp);
519
- else res.sendAuthWrap(req.__(`Sign up`), resp, { methods: [] });
517
+ if (signup_form.default_render_page) {
518
+ const page = Page.findOne({ name: signup_form.default_render_page });
519
+ res.sendWrap(
520
+ { title: req.__(`Sign up`), no_menu: page?.attributes?.no_menu },
521
+ resp
522
+ );
523
+ } else res.sendAuthWrap(req.__(`Sign up`), resp, { methods: [] });
520
524
  }
521
525
  } else await defaultSignup();
522
526
  })
@@ -786,6 +790,7 @@ router.post(
786
790
  res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
787
791
  return;
788
792
  }
793
+ uobj.role_id = +getState().getConfig("signup_role", "80");
789
794
  const u = await User.create(uobj);
790
795
  await send_verification_email(u, req);
791
796
 
@@ -843,6 +848,7 @@ router.post(
843
848
  res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
844
849
  } else {
845
850
  try {
851
+ form.values.role_id = +getState().getConfig("signup_role", "80");
846
852
  const u = await User.create(form.values);
847
853
  await send_verification_email(u, req);
848
854
 
@@ -978,6 +984,7 @@ router.post(
978
984
  getAuthLinks("signup", true)
979
985
  );
980
986
  } else {
987
+ userObject.role_id = +getState().getConfig("signup_role", "80");
981
988
  const u = await User.create(userObject);
982
989
  await send_verification_email(u, req);
983
990
 
@@ -1010,6 +1017,7 @@ router.post(
1010
1017
  form.values.password = password;
1011
1018
  res.sendAuthWrap(new_user_form, form, getAuthLinks("signup", true));
1012
1019
  } else {
1020
+ form.values.role_id = +getState().getConfig("signup_role", "80");
1013
1021
  const u = await User.create(form.values);
1014
1022
  await send_verification_email(u, req);
1015
1023
  if (req.smr)
package/errors.js CHANGED
@@ -37,6 +37,7 @@ module.exports =
37
37
  const createCrash = severity <= 3;
38
38
  //console.error(err.stack);
39
39
  if (!(devmode && log_sql) && createCrash) await Crash.create(err, req);
40
+ else console.error(err);
40
41
 
41
42
  if (req.xhr) {
42
43
  res
@@ -0,0 +1,13 @@
1
+ The Cordova builder is a docker image with all dependencies to build Android apps.
2
+ It can be pulled from dockerhub while installing Saltcorn, or you can use the pull button.
3
+
4
+ Please make sure your server has a valid and accessible docker daemon running.
5
+
6
+ For this, either set up a standard docker installation
7
+ or use the [docker rootless mode](https://docs.docker.com/engine/security/rootless/) (recommended).
8
+
9
+ In a standard docker environment, you need root user privileges.
10
+ For this, you can add the user running Saltcorn to the docker group,
11
+ or if you already have the privileges, you are ready to go.
12
+
13
+ A docker daemon in rootless mode doesn't need any further configuration.
package/load_plugins.js CHANGED
@@ -6,59 +6,10 @@
6
6
  * @module load_plugins
7
7
  */
8
8
  const db = require("@saltcorn/data/db");
9
- const { PluginManager } = require("live-plugin-manager");
10
9
  const { getState, getRootState } = require("@saltcorn/data/db/state");
11
10
  const Plugin = require("@saltcorn/data/models/plugin");
12
- const fs = require("fs");
13
- const proc = require("child_process");
14
- const tmp = require("tmp-promise");
15
11
 
16
- const staticDependencies = {
17
- "@saltcorn/markup": require("@saltcorn/markup"),
18
- "@saltcorn/markup/tags": require("@saltcorn/markup/tags"),
19
- "@saltcorn/markup/layout": require("@saltcorn/markup/layout"),
20
- "@saltcorn/markup/helpers": require("@saltcorn/markup/helpers"),
21
- "@saltcorn/markup/layout_utils": require("@saltcorn/markup/layout_utils"),
22
- "@saltcorn/data": require("@saltcorn/data"),
23
- "@saltcorn/data/db": require("@saltcorn/data/db"),
24
- "@saltcorn/data/utils": require("@saltcorn/data/utils"),
25
- "@saltcorn/data/db/state": require("@saltcorn/data/db/state"),
26
- "@saltcorn/data/plugin-helper": require("@saltcorn/data/plugin-helper"),
27
- "@saltcorn/data/plugin-testing": require("@saltcorn/data/plugin-testing"),
28
- "@saltcorn/data/models/field": require("@saltcorn/data/models/field"),
29
- "@saltcorn/data/models/fieldrepeat": require("@saltcorn/data/models/fieldrepeat"),
30
- "@saltcorn/data/models/table": require("@saltcorn/data/models/table"),
31
- "@saltcorn/data/models/form": require("@saltcorn/data/models/form"),
32
- "@saltcorn/data/models/discovery": require("@saltcorn/data/models/discovery"),
33
- "@saltcorn/data/models/config": require("@saltcorn/data/models/config"),
34
- "@saltcorn/data/models/library": require("@saltcorn/data/models/library"),
35
- "@saltcorn/data/models/model": require("@saltcorn/data/models/model"),
36
- "@saltcorn/data/models/model_instance": require("@saltcorn/data/models/model_instance"),
37
- "@saltcorn/data/models/notification": require("@saltcorn/data/models/notification"),
38
- "@saltcorn/data/models/role": require("@saltcorn/data/models/role"),
39
- "@saltcorn/data/models/tag": require("@saltcorn/data/models/tag"),
40
- "@saltcorn/data/models/tag_entry": require("@saltcorn/data/models/tag_entry"),
41
- "@saltcorn/data/models/view": require("@saltcorn/data/models/view"),
42
- "@saltcorn/data/models/page": require("@saltcorn/data/models/page"),
43
- "@saltcorn/data/models/file": require("@saltcorn/data/models/file"),
44
- "@saltcorn/data/models/user": require("@saltcorn/data/models/user"),
45
- "@saltcorn/data/models/layout": require("@saltcorn/data/models/layout"),
46
- "@saltcorn/data/models/expression": require("@saltcorn/data/models/expression"),
47
- "@saltcorn/data/models/workflow": require("@saltcorn/data/models/workflow"),
48
- imapflow: require("imapflow"),
49
- "node-fetch": require("node-fetch"),
50
- };
51
-
52
- /**
53
- * Create plugin manager with default list of core plugins
54
- * @type {PluginManager}
55
- */
56
- const defaultManager = new PluginManager({
57
- staticDependencies: {
58
- contractis: require("contractis"),
59
- ...staticDependencies,
60
- },
61
- });
12
+ const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
62
13
 
63
14
  /**
64
15
  * Load one plugin
@@ -68,19 +19,35 @@ const defaultManager = new PluginManager({
68
19
  */
69
20
  const loadPlugin = async (plugin, force) => {
70
21
  // load plugin
71
- const res = await requirePlugin(plugin, force);
22
+ const loader = new PluginInstaller(plugin);
23
+ const res = await loader.install(force);
72
24
  const configuration =
73
25
  typeof plugin.configuration === "string"
74
26
  ? JSON.parse(plugin.configuration)
75
27
  : plugin.configuration;
76
- // register plugin
77
- getState().registerPlugin(
78
- res.plugin_module.plugin_name || plugin.name,
79
- res.plugin_module,
80
- configuration,
81
- res.location,
82
- res.name
83
- );
28
+ try {
29
+ // register plugin
30
+ getState().registerPlugin(
31
+ res.plugin_module.plugin_name || plugin.name,
32
+ res.plugin_module,
33
+ configuration,
34
+ res.location,
35
+ res.name
36
+ );
37
+ } catch (error) {
38
+ if (force) {
39
+ // remove the install dir and try again
40
+ await loader.remove();
41
+ await loader.install(force);
42
+ getState().registerPlugin(
43
+ res.plugin_module.plugin_name || plugin.name,
44
+ res.plugin_module,
45
+ configuration,
46
+ res.location,
47
+ res.name
48
+ );
49
+ }
50
+ }
84
51
  if (res.plugin_module.onLoad) {
85
52
  try {
86
53
  await res.plugin_module.onLoad(plugin.configuration);
@@ -91,105 +58,48 @@ const loadPlugin = async (plugin, force) => {
91
58
  return res;
92
59
  };
93
60
 
94
- /**
95
- * Git pull or clone
96
- * @param plugin
97
- */
98
- const gitPullOrClone = async (plugin) => {
99
- await fs.promises.mkdir("git_plugins", { recursive: true });
100
- let keyfnm,
101
- setKey = `-c core.sshCommand="ssh -oBatchMode=yes -o 'StrictHostKeyChecking no'" `;
102
- if (plugin.deploy_private_key) {
103
- keyfnm = await tmp.tmpName();
104
- await fs.promises.writeFile(
105
- keyfnm,
106
- plugin.deploy_private_key.replace(/[\r]+/g, "") + "\n",
107
- {
108
- mode: 0o600,
109
- encoding: "ascii",
110
- }
111
- );
112
- setKey = `-c core.sshCommand="ssh -oBatchMode=yes -o 'StrictHostKeyChecking no' -i ${keyfnm}" `;
113
- }
114
- const dir = `git_plugins/${plugin.name}`;
115
- if (fs.existsSync(dir)) {
116
- proc.execSync(`git ${setKey} -C ${dir} pull`);
117
- } else {
118
- proc.execSync(`git ${setKey} clone ${plugin.location} ${dir}`);
119
- }
120
- if (plugin.deploy_private_key) await fs.promises.unlink(keyfnm);
121
- return dir;
122
- };
123
61
  /**
124
62
  * Install plugin
125
63
  * @param plugin - plugin name
126
64
  * @param force - force flag
127
- * @param manager - plugin manager
128
65
  * @returns {Promise<{plugin_module: *}|{plugin_module: any}>}
129
66
  */
130
- const requirePlugin = async (plugin, force, manager = defaultManager) => {
131
- const installed_plugins = (await manager.list()).map((p) => p.name);
132
- // todo as idea is to make list of mandatory plugins configurable
133
- if (
134
- ["@saltcorn/base-plugin", "@saltcorn/sbadmin2"].includes(plugin.location)
135
- ) {
136
- return { plugin_module: require(plugin.location) };
137
- } else if (plugin.source === "npm") {
138
- if (force || !installed_plugins.includes(plugin.location)) {
139
- const plinfo = await manager.install(plugin.location, plugin.version);
140
- return { plugin_module: manager.require(plugin.location), ...plinfo };
141
- } else {
142
- const plinfo = manager.getInfo(plugin.location);
143
- return { plugin_module: manager.require(plugin.location), ...plinfo };
144
- }
145
- } else if (plugin.source === "local") {
146
- const plinfo = await manager.installFromPath(plugin.location, {
147
- force: true,
148
- });
149
- return { plugin_module: manager.require(plugin.name), ...plinfo };
150
- } else if (plugin.source === "git") {
151
- const loc = await gitPullOrClone(plugin);
152
- const plinfo = await manager.installFromPath(loc, {
153
- force: true,
154
- });
155
- return { plugin_module: manager.require(plugin.name), ...plinfo };
156
- } else if (plugin.source === "github") {
157
- if (force || !installed_plugins.includes(plugin.location)) {
158
- const plinfo = await manager.installFromGithub(plugin.location, {
159
- force: true,
160
- });
161
- return { plugin_module: manager.require(plugin.name), ...plinfo };
162
- } else {
163
- const plinfo = manager.getInfo(plugin.location);
164
- return { plugin_module: manager.require(plugin.location), ...plinfo };
165
- }
166
- } else throw new Error("Unknown plugin source: " + plugin.source);
67
+ const requirePlugin = async (plugin, force) => {
68
+ const loader = new PluginInstaller(plugin);
69
+ return await loader.install(force);
167
70
  };
71
+
168
72
  /**
169
73
  * Load all plugins
170
74
  * @returns {Promise<void>}
171
75
  */
172
- const loadAllPlugins = async () => {
76
+ const loadAllPlugins = async (force) => {
173
77
  await getState().refresh(true);
174
78
  const plugins = await db.select("_sc_plugins");
175
79
  for (const plugin of plugins) {
176
80
  try {
177
- await loadPlugin(plugin);
81
+ await loadPlugin(plugin, force);
178
82
  } catch (e) {
179
83
  console.error(e);
180
84
  }
181
85
  }
182
86
  await getState().refresh(true);
183
87
  };
88
+
184
89
  /**
185
90
  * Load Plugin and its dependencies and save into local installation
186
91
  * @param plugin
187
92
  * @param force
188
93
  * @param noSignalOrDB
189
- * @param manager - optional plugin manager
94
+ * @param __ translation function
190
95
  * @returns {Promise<void>}
191
96
  */
192
- const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
97
+ const loadAndSaveNewPlugin = async (
98
+ plugin,
99
+ force,
100
+ noSignalOrDB,
101
+ __ = (str) => str
102
+ ) => {
193
103
  const tenants_unsafe_plugins = getRootState().getConfig(
194
104
  "tenants_unsafe_plugins",
195
105
  false
@@ -215,11 +125,10 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
215
125
  return;
216
126
  }
217
127
  }
218
- const { version, plugin_module, location } = await requirePlugin(
219
- plugin,
220
- force,
221
- manager
222
- );
128
+ const msgs = [];
129
+ const loader = new PluginInstaller(plugin);
130
+ const { version, plugin_module, location, loadedWithReload } =
131
+ await loader.install(force);
223
132
 
224
133
  // install dependecies
225
134
  for (const loc of plugin_module.dependencies || []) {
@@ -232,13 +141,43 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
232
141
  );
233
142
  }
234
143
  }
235
- getState().registerPlugin(
236
- plugin_module.plugin_name || plugin.name,
237
- plugin_module,
238
- plugin.configuration,
239
- location,
240
- plugin.name
241
- );
144
+ let registeredWithReload = false;
145
+ try {
146
+ getState().registerPlugin(
147
+ plugin_module.plugin_name || plugin.name,
148
+ plugin_module,
149
+ plugin.configuration,
150
+ location,
151
+ plugin.name
152
+ );
153
+ } catch (error) {
154
+ if (force) {
155
+ getState().log(
156
+ 2,
157
+ `Error registering plugin ${plugin.name}. Removing and trying again.`
158
+ );
159
+ await loader.remove();
160
+ await loader.install(force);
161
+ getState().registerPlugin(
162
+ plugin_module.plugin_name || plugin.name,
163
+ plugin_module,
164
+ plugin.configuration,
165
+ location,
166
+ plugin.name
167
+ );
168
+ registeredWithReload = true;
169
+ } else {
170
+ throw error;
171
+ }
172
+ }
173
+ if (loadedWithReload || registeredWithReload) {
174
+ msgs.push(
175
+ __(
176
+ "The plugin was corrupted and had to be repaired. We recommend restarting your server.",
177
+ plugin.name
178
+ )
179
+ );
180
+ }
242
181
  if (plugin_module.onLoad) {
243
182
  try {
244
183
  await plugin_module.onLoad(plugin.configuration);
@@ -252,9 +191,10 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
252
191
  getState().processSend({
253
192
  installPlugin: plugin,
254
193
  tenant: db.getTenantSchema(),
255
- force,
194
+ force: false, // okay ??
256
195
  });
257
196
  }
197
+ return msgs;
258
198
  };
259
199
 
260
200
  module.exports = {
@@ -262,5 +202,4 @@ module.exports = {
262
202
  loadAllPlugins,
263
203
  loadPlugin,
264
204
  requirePlugin,
265
- staticDependencies,
266
205
  };
package/locales/en.json CHANGED
@@ -1382,5 +1382,14 @@
1382
1382
  "Open a connection to TLS server with self-signed or invalid TLS certificate": "Open a connection to TLS server with self-signed or invalid TLS certificate",
1383
1383
  "Allow self-signed": "Allow self-signed",
1384
1384
  "Optionally associate a table with this trigger": "Optionally associate a table with this trigger",
1385
- "Delete table": "Delete table"
1386
- }
1385
+ "Delete table": "Delete table",
1386
+ "Signup role": "Signup role",
1387
+ "The initial role of signed up users": "The initial role of signed up users",
1388
+ "Cordova builder": "Cordova builder",
1389
+ "not available": "not available",
1390
+ "pull": "pull",
1391
+ "refresh": "refresh",
1392
+ "installed": "installed",
1393
+ "Include table history in backup": "Include table history in backup",
1394
+ "The plugin was corrupted and had to be repaired. We recommend restarting your server.": "The plugin was corrupted and had to be repaired. We recommend restarting your server."
1395
+ }
package/markup/admin.js CHANGED
@@ -23,6 +23,8 @@ const Form = require("@saltcorn/data/models/form");
23
23
  const Table = require("@saltcorn/data/models/table");
24
24
  const View = require("@saltcorn/data/models/view");
25
25
  const User = require("@saltcorn/data/models/user");
26
+ const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
27
+ const Field = require("@saltcorn/data/models/field");
26
28
 
27
29
  /**
28
30
  * Restore Backup
@@ -437,6 +439,26 @@ const config_fields_form = async ({
437
439
  const label = configTypes[name].label || name;
438
440
  const sublabel = configTypes[name].sublabel || configTypes[name].blurb;
439
441
 
442
+ if (configTypes[name].type === "Repeat") {
443
+ const repFields = configTypes[name].fields;
444
+ const filledFields = [];
445
+ for (const rfield of repFields) {
446
+ const fillField = new Field(rfield);
447
+ await fillField.fill_fkey_options();
448
+ filledFields.push(fillField);
449
+ }
450
+ fields.push(
451
+ new FieldRepeat({
452
+ name,
453
+ label,
454
+ fields: filledFields,
455
+ showIf,
456
+ })
457
+ );
458
+
459
+ continue;
460
+ }
461
+
440
462
  fields.push({
441
463
  name,
442
464
  ...configTypes[name],
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.5-beta.0",
3
+ "version": "0.9.5-beta.10",
4
4
  "description": "Server app for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "main": "index.js",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
9
  "@aws-sdk/client-s3": "^3.451.0",
10
- "@saltcorn/base-plugin": "0.9.5-beta.0",
11
- "@saltcorn/builder": "0.9.5-beta.0",
12
- "@saltcorn/data": "0.9.5-beta.0",
13
- "@saltcorn/admin-models": "0.9.5-beta.0",
14
- "@saltcorn/filemanager": "0.9.5-beta.0",
15
- "@saltcorn/markup": "0.9.5-beta.0",
16
- "@saltcorn/sbadmin2": "0.9.5-beta.0",
10
+ "@saltcorn/base-plugin": "0.9.5-beta.10",
11
+ "@saltcorn/builder": "0.9.5-beta.10",
12
+ "@saltcorn/data": "0.9.5-beta.10",
13
+ "@saltcorn/admin-models": "0.9.5-beta.10",
14
+ "@saltcorn/filemanager": "0.9.5-beta.10",
15
+ "@saltcorn/markup": "0.9.5-beta.10",
16
+ "@saltcorn/plugins-loader": "0.9.5-beta.10",
17
+ "@saltcorn/sbadmin2": "0.9.5-beta.10",
17
18
  "@socket.io/cluster-adapter": "^0.2.1",
18
19
  "@socket.io/sticky": "^1.0.1",
19
20
  "adm-zip": "0.5.10",
@@ -26,6 +27,7 @@
26
27
  "cors": "2.8.5",
27
28
  "csurf": "^1.11.0",
28
29
  "csv-stringify": "^5.5.0",
30
+ "dockerode": "~4.0.2",
29
31
  "express": "^4.17.1",
30
32
  "express-fileupload": "^1.1.8",
31
33
  "express-promise-router": "^3.0.3",
@@ -37,7 +39,6 @@
37
39
  "i18n": "^0.15.1",
38
40
  "imapflow": "1.0.123",
39
41
  "jsonwebtoken": "^9.0.0",
40
- "live-plugin-manager": "^0.17.1",
41
42
  "markdown-it": "^13.0.2",
42
43
  "moment": "^2.29.4",
43
44
  "multer": "1.4.5-lts.1",
@@ -70,8 +71,8 @@
70
71
  },
71
72
  "repository": "github:saltcorn/saltcorn",
72
73
  "devDependencies": {
73
- "jest": "26.6.3",
74
- "jest-environment-jsdom": "^26.6.2",
74
+ "jest": "^28.1.3",
75
+ "jest-environment-jsdom": "28.1.3",
75
76
  "supertest": "^6.3.3"
76
77
  },
77
78
  "scripts": {
@@ -84,11 +85,13 @@
84
85
  "testEnvironment": "node",
85
86
  "testPathIgnorePatterns": [
86
87
  "/node_modules/",
87
- "/plugin_packages/"
88
+ "/plugin_packages/",
89
+ "/plugins_folder/"
88
90
  ],
89
91
  "coveragePathIgnorePatterns": [
90
92
  "/node_modules/",
91
- "/plugin_packages/"
93
+ "/plugin_packages/",
94
+ "/plugins_folder/"
92
95
  ],
93
96
  "moduleNameMapper": {
94
97
  "@saltcorn/sqlite/(.*)": "<rootDir>/../sqlite/dist/$1",