@saltcorn/server 0.7.4-beta.2 → 0.7.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.
package/app.js CHANGED
@@ -85,6 +85,8 @@ const getApp = async (opts = {}) => {
85
85
  const development_mode = getState().getConfig("development_mode", false);
86
86
  // switch on sql logging - but it was initiated before???
87
87
  if (getState().getConfig("log_sql", false)) db.set_sql_logging();
88
+ // for multi-tenant with localhost, we need 1 instead of the default of 2
89
+ if (opts.subdomainOffset) app.set("subdomain offset", opts.subdomainOffset);
88
90
 
89
91
  // https://www.npmjs.com/package/helmet
90
92
  // helmet is secure app by adding HTTP headers
@@ -225,8 +227,9 @@ const getApp = async (opts = {}) => {
225
227
  })
226
228
  );
227
229
  passport.use(
228
- new JwtStrategy(jwtOpts, (jwt_payload, done) => {
229
- User.findOne({ email: jwt_payload.sub }).then((u) => {
230
+ new JwtStrategy(jwtOpts, async (jwt_payload, done) => {
231
+ const userCheck = async () => {
232
+ const u = await User.findOne({ email: jwt_payload.sub });
230
233
  if (
231
234
  u &&
232
235
  u.last_mobile_login &&
@@ -242,7 +245,16 @@ const getApp = async (opts = {}) => {
242
245
  } else {
243
246
  return done(null, { role_id: 10 });
244
247
  }
245
- });
248
+ };
249
+ if (
250
+ db.is_it_multi_tenant() &&
251
+ jwt_payload.tenant?.length > 0 &&
252
+ jwt_payload.tenant !== db.connectObj.default_schema
253
+ ) {
254
+ return await db.runWithTenant(jwt_payload.tenant, userCheck);
255
+ } else {
256
+ return await userCheck();
257
+ }
246
258
  })
247
259
  );
248
260
  passport.use(
@@ -259,6 +271,12 @@ const getApp = async (opts = {}) => {
259
271
  passport.deserializeUser(function (user, done) {
260
272
  done(null, user);
261
273
  });
274
+ app.use(function (req, res, next) {
275
+ if (req.headers["x-saltcorn-client"] === "mobile-app") {
276
+ req.smr = true; // saltcorn-mobile-request
277
+ }
278
+ return next();
279
+ });
262
280
  app.use(setTenant);
263
281
 
264
282
  // Change into s3storage compatible selector
@@ -267,12 +285,15 @@ const getApp = async (opts = {}) => {
267
285
  app.use(s3storage.middlewareTransform);
268
286
 
269
287
  app.use(wrapper(version_tag));
288
+
270
289
  const csurf = csrf();
271
290
  if (!opts.disableCsrf)
272
291
  app.use(function (req, res, next) {
273
292
  if (
274
- req.url.startsWith("/api/") ||
275
- req.url === "/auth/login-with/jwt" ||
293
+ (req.smr &&
294
+ (req.url.startsWith("/api/") ||
295
+ req.url === "/auth/login-with/jwt" ||
296
+ req.url === "/auth/signup")) ||
276
297
  jwt_extractor(req)
277
298
  )
278
299
  return disabledCsurf(req, res, next);
@@ -280,13 +301,6 @@ const getApp = async (opts = {}) => {
280
301
  });
281
302
  else app.use(disabledCsurf);
282
303
 
283
- app.use(function (req, res, next) {
284
- if (req.headers["x-saltcorn-client"] === "mobile-app") {
285
- req.smr = true; // saltcorn-mobile-request
286
- }
287
- return next();
288
- });
289
-
290
304
  mountRoutes(app);
291
305
  // set tenant homepage as / root
292
306
  app.get("/", error_catcher(homepage));
package/auth/routes.js CHANGED
@@ -199,33 +199,41 @@ const getAuthLinks = (current, noMethods) => {
199
199
  return links;
200
200
  };
201
201
 
202
- const loginWithJwt = async (email, password, res) => {
203
- const user = await User.findOne({ email });
204
- if (user && user.checkPassword(password)) {
205
- const now = new Date();
206
- const jwt_secret = db.connectObj.jwt_secret;
207
- const token = jwt.sign(
208
- {
209
- sub: email,
210
- user: {
211
- id: user.id,
212
- email: user.email,
213
- role_id: user.role_id,
214
- language: user.language ? user.language : "en",
215
- disabled: user.disabled,
202
+ const loginWithJwt = async (email, password, saltcornApp, res) => {
203
+ const loginFn = async () => {
204
+ const user = await User.findOne({ email });
205
+ if (user && user.checkPassword(password)) {
206
+ const now = new Date();
207
+ const jwt_secret = db.connectObj.jwt_secret;
208
+ const token = jwt.sign(
209
+ {
210
+ sub: email,
211
+ user: {
212
+ id: user.id,
213
+ email: user.email,
214
+ role_id: user.role_id,
215
+ language: user.language ? user.language : "en",
216
+ disabled: user.disabled,
217
+ },
218
+ iss: "saltcorn@saltcorn",
219
+ aud: "saltcorn-mobile-app",
220
+ iat: now.valueOf(),
221
+ tenant: db.getTenantSchema(),
216
222
  },
217
- iss: "saltcorn@saltcorn",
218
- aud: "saltcorn-mobile-app",
219
- iat: now.valueOf(),
220
- },
221
- jwt_secret
222
- );
223
- if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
224
- res.json(token);
223
+ jwt_secret
224
+ );
225
+ if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
226
+ res.json(token);
227
+ } else {
228
+ res.json({
229
+ alerts: [{ type: "danger", msg: "Incorrect user or password" }],
230
+ });
231
+ }
232
+ };
233
+ if (saltcornApp && saltcornApp !== db.connectObj.default_schema) {
234
+ await db.runWithTenant(saltcornApp, loginFn);
225
235
  } else {
226
- res.json({
227
- alerts: [{ type: "danger", msg: "Incorrect user or password" }],
228
- });
236
+ await loginFn();
229
237
  }
230
238
  };
231
239
 
@@ -899,7 +907,13 @@ router.post(
899
907
  } else {
900
908
  const u = await User.create({ email, password });
901
909
  await send_verification_email(u, req);
902
- if (req.smr) await loginWithJwt(email, password, res);
910
+ if (req.smr)
911
+ await loginWithJwt(
912
+ email,
913
+ password,
914
+ req.headers["x-saltcorn-app"],
915
+ res
916
+ );
903
917
  else signup_login_with_user(u, req, res);
904
918
  }
905
919
  }
@@ -1008,7 +1022,7 @@ router.get(
1008
1022
  const { method } = req.params;
1009
1023
  if (method === "jwt") {
1010
1024
  const { email, password } = req.query;
1011
- await loginWithJwt(email, password, res);
1025
+ await loginWithJwt(email, password, req.headers["x-saltcorn-app"], res);
1012
1026
  } else {
1013
1027
  const auth = getState().auth_methods[method];
1014
1028
  if (auth) {
package/locales/en.json CHANGED
@@ -951,7 +951,6 @@
951
951
  "Modules up-to-date": "Modules up-to-date",
952
952
  "User must have this role or higher to read rows from the table, unless they are the owner": "User must have this role or higher to read rows from the table, unless they are the owner",
953
953
  "User must have this role or higher to edit or create new rows in the table, unless they are the owner": "User must have this role or higher to edit or create new rows in the table, unless they are the owner",
954
- "Tag": "Tag",
955
954
  "Tagname": "Tagname",
956
955
  "Create tag": "Create tag",
957
956
  "Tags": "Tags",
@@ -965,6 +964,33 @@
965
964
  "Trigger": "Trigger",
966
965
  "Add %s to tag": "Add %s to tag",
967
966
  "Tag %s deleted": "Tag %s deleted",
968
- "Tag %s created": "Tag %s created"
969
-
970
- }
967
+ "Tag %s created": "Tag %s created",
968
+ "Application diagram": "Application diagram",
969
+ "Diagram": "Diagram",
970
+ "Entry point": "Entry point",
971
+ "Platform": "Platform",
972
+ "docker": "docker",
973
+ "android": "android",
974
+ "iOS": "iOS",
975
+ "App file": "App file",
976
+ "Server URL": "Server URL",
977
+ "Module %s installed, please complete configuration.": "Module %s installed, please complete configuration.",
978
+ "Module %s removed.": "Module %s removed.",
979
+ "Module %s installed": "Module %s installed",
980
+ "Upgrading modules...": "Upgrading modules...",
981
+ "Backup now": "Backup now",
982
+ "Snapshot now": "Snapshot now",
983
+ "Restore/download automated backups »": "Restore/download automated backups »",
984
+ "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).",
985
+ "List/download snapshots &raquo;": "List/download snapshots &raquo;",
986
+ "Discover tables that are already in the Database, but not known to Saltcorn": "Discover tables that are already in the Database, but not known to Saltcorn",
987
+ "Split paste": "Split paste",
988
+ "Separate paste content into separate inputs": "Separate paste content into separate inputs",
989
+ "Add entries to tag": "Add entries to tag",
990
+ "Add pages": "Add pages",
991
+ "Add triggers": "Add triggers",
992
+ "Formula value": "Formula value",
993
+ "The build was successfully": "The build was successfully",
994
+ "Unable to build the app:": "Unable to build the app:",
995
+ "Add tag": "Add tag"
996
+ }
package/locales/ru.json CHANGED
@@ -180,7 +180,7 @@
180
180
  "Pack %s uninstalled": "Пакет %s удален",
181
181
  "Uninstall": "Удалить",
182
182
  "Result preview for ": "Предварительный просмотр результатов для ",
183
- "Choose views for <a href=\"/search\">search results</a> for each table.<br/>Set to blank to omit table from global search.": "Выберите для каждой таблицы представление для вывода <a href=\"/search\">результатов поиска</a>.<br/>Оставьте незаполненным, если не хотите, чтобы таблица отображалась в глобальном поиске.",
183
+ "Choose views for <a href=\"/search\">search results</a> for each table.<br/>Set to blank to omit table from global search.": "Выберите для каждой таблицы представление для вывода <a href=\"/search\">результатов поиска</a>.<br/>Оставьте незаполненным, если не хотите, чтобы таблица отображалась в глобальном поиске.",
184
184
  "These tables lack suitable views: ": "Указанные таблицы не имеют представлений с поддержкой поиска: ",
185
185
  "Search configuration": "Настройки поиска",
186
186
  "Table saved with version history enabled": "Таблица сохранена с включенной историей версий",
@@ -303,7 +303,7 @@
303
303
  "Clone": "Клонировать",
304
304
  "View %s cloned as %s": "Представление %s клонировано в %s",
305
305
  "Duplicate": "Дублировать",
306
- "View %s duplicated as %s": "Представление %s задублировано в %s",
306
+ "View %s duplicated as %s": "Представление %s дублировано в %s",
307
307
  "The view name will appear as the title of pop-ups showing this view, and in the URL when it is shown alone.": "Имя представления отображается как заголовок всплывающего окна или в URL, в зависимости от режима отображения.",
308
308
  "Saltcorn version": "версия платформы",
309
309
  "Node.js version": "версия Node.js",
@@ -337,7 +337,7 @@
337
337
  "Subtables": "Подчиненные таблицы",
338
338
  "List View": "Представление Список (List)",
339
339
  "Show View": "Представление Show",
340
- "Which related tables would you like to show in sub-lists below the selected item?": "Which related tables would you like to show in sub-lists below the selected item?",
340
+ "Which related tables would you like to show in sub-lists below the selected item?": "Какие связанные таблицы вы хотите отобразить в подчиненных списках (sub-lists) под выбранной записью?",
341
341
  "Order and layout": "Сортировка и Макет",
342
342
  "Order by": "Сортировка по",
343
343
  "Descending": "В обратном порядке",
@@ -801,11 +801,11 @@
801
801
  "Expiration in days": "Срок хранения (в днях)",
802
802
  "Delete old backup files in this directory after the set number of days": "Старые резервные копии в этой папке будут удалять после истечения срока хранения ",
803
803
  "Backup settings updated": "Настройки резервного копирования обновлены",
804
- "Periodic snapshots enabled": "Регулярные снимки (снэпшоты) активированы",
805
- "Snapshot will be made every hour if there are changes": "Снимки (снэпшоты) будут выполняться каждый час",
804
+ "Periodic snapshots enabled": "Регулярные снимки (снепшоты)",
805
+ "Snapshot will be made every hour if there are changes": "Снимки (снепшоты) будут выполняться каждый час",
806
806
  "Manual backup": "Ручное резервное копирование",
807
807
  "Automated backup": "Автоматическое резервное копирование",
808
- "Snapshots": "Снимки (снэпшоты)",
808
+ "Snapshots": "Снимки (снепшоты)",
809
809
  "Mobile app": "Мобильное приложение",
810
810
  "Module store": "Магазин модулей",
811
811
  "Upgrade installed modules": "Обновить установленные модули",
@@ -829,5 +829,58 @@
829
829
  "App file": "Файл приложения",
830
830
  "Server URL": "URL сервера",
831
831
  "android": "android",
832
- "iOS": "iOS"
832
+ "iOS": "iOS",
833
+ "Tag %s created": "Тег %s создан",
834
+ "%s Tag": "%s Тег",
835
+ "Add tables": "Добавить таблицы",
836
+ "Add views": "Добавить представления",
837
+ "Remove From Tag": "Удалить из тега",
838
+ "Add tages": "Добавить теги",
839
+ "Trigger": "Триггер",
840
+ "Tag": "Тег",
841
+ "Download snapshots": "Скачать снепшоты",
842
+ "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Снепшоты сохраняют структуру приложения, но не сохраняют данные в таблицах. Конкретные представления и страницы могут быть восстановлены по ссылке <a href='/viewedit'>view</a> или <a href='/pageedit'>pages</a> (Кнопка \"Восстановить\" в выпадающем меню представления или страницы).",
843
+ "List/download snapshots &raquo;": "Список снепшотов &raquo;",
844
+ "Add %s to tag": "Добавить %s к тегу",
845
+ "Tagname": "Имя тега",
846
+ "Create tag": "Создать тег",
847
+ "Tags": "Теги",
848
+ "New tag": "Новый тег",
849
+ "Tag name": "Имя тега",
850
+ "Tag %s deleted": "Тег %s удален",
851
+ "Add entries to tag": "Добавить объекты в тег",
852
+ "Please select at least one item": "Пожалуйста добавьте хотя бы один объект",
853
+ "Backup now": "Сделать бекап сейчас",
854
+ "Snapshot now": "Сделать снепшот сейчас",
855
+ "Restore/download automated backups &raquo;": "Восстановить/скачать резервные копии &raquo;",
856
+ "No changes detected, snapshot skipped": "Нет изменений, снепшот пропущен",
857
+ "Pattern": "Паттерн",
858
+ "Add pages": "Добавить страницы",
859
+ "Add triggers": "Добавить триггеры",
860
+ "Edit Module": "Настроить Модуль",
861
+ "Page %s duplicated as %s": "Страница %s дублирована в %s",
862
+ "Auto save": "Автосохранение",
863
+ "Save any changes immediately": "Сохранять все изменения данных в форме немедленно",
864
+ "This is the view to which the user will be sent when the form is submitted. The view you specify here can be ignored depending on the context of the form, for instance if it appears in a pop-up the redirect will not take place.": "Указывается представление, куда будет перенаправлен пользователь после отправки формы. В некоторых случаях перенаправление не будет выполнено, например, если форма использована в режиме всплывающего окна.",
865
+ "Destination view": "Destination view",
866
+ "Destination URL Formula": "Destination URL Formula",
867
+ "Back": "Назад",
868
+ "Backup successful": "Резервная копия успешно создана",
869
+ "Save before going back": "Save before going back",
870
+ "Reload after going back": "Reload after going back",
871
+ "Steps to go back": "Steps to go back",
872
+ "View pattern": "Паттерн представления",
873
+ "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view": "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view",
874
+ "This will delete <strong>EVERYTHING</strong> in the selected categories": "Внимание! Будут удалены <strong>ВСЕ</strong> данные в выбранных категориях",
875
+ "Tag not found": "Тэг не найден",
876
+ "Build Result": "Результат сборки",
877
+ "Download automated backup": "Автоматически созданные резервные копии",
878
+ "Restoring automated backup": "Восстановление из автоматически созданной резервной копии",
879
+ "Download one of the backups above": "Скачать один из бекапов (файлов с резервной копией) выше",
880
+ "Clear this application": "Удалить ВСЕ данные в текущем приложении",
881
+ "(tick all boxes)": "(указав все галочки)",
882
+ "When prompted to create the first user, click the link to restore a backup": "Вместо создания первого пользователя, выберите Восстановление из резервной копии (restore a backup)",
883
+ "Select the downloaded backup file": "Выбрать скаченный бекап (файл с резервной копией)",
884
+ "Snapshot successful": "Снимок (Снепшот) выполнен успешно",
885
+ "Unable to build the app:": "Ошибка создания приложения:"
833
886
  }
package/markup/admin.js CHANGED
@@ -203,6 +203,8 @@ const send_infoarch_page = (args) => {
203
203
  { text: "Multitenancy", href: "/tenant/settings" },
204
204
  ]
205
205
  : []),
206
+ { text: "Tags", href: "/tag" },
207
+ { text: "Diagram", href: "/diagram" },
206
208
  ],
207
209
  ...args,
208
210
  });
@@ -288,10 +290,7 @@ const send_admin_page = (args) => {
288
290
  { text: "Backup", href: "/admin/backup" },
289
291
  { text: "Email", href: "/admin/email" },
290
292
  { text: "System", href: "/admin/system" },
291
- ...(isRoot ? [{ text: "Tag", href: "/tag" }] : []),
292
- ...(isRoot
293
- ? [{ text: "Mobile app", href: "/admin/build-mobile-app" }]
294
- : []),
293
+ { text: "Mobile app", href: "/admin/build-mobile-app" },
295
294
  ],
296
295
  ...args,
297
296
  });
@@ -146,7 +146,7 @@ const boolExamples = (type, fields) => {
146
146
  * @returns {p[]}
147
147
  */
148
148
  const expressionBlurb = (type, stored, allFields, req) => {
149
- const fields = allFields.filter((f) => !f.is_fkey && !f.calculated);
149
+ const fields = allFields.filter((f) => !f.calculated);
150
150
  const funs = getState().functions;
151
151
  const funNames = Object.entries(funs)
152
152
  .filter(([k, v]) => !(!stored && v.isAsync))
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.7.4-beta.2",
3
+ "version": "0.7.4",
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
- "@saltcorn/base-plugin": "0.7.4-beta.2",
10
- "@saltcorn/builder": "0.7.4-beta.2",
11
- "@saltcorn/data": "0.7.4-beta.2",
12
- "@saltcorn/admin-models": "0.7.4-beta.2",
13
- "@saltcorn/markup": "0.7.4-beta.2",
14
- "@saltcorn/sbadmin2": "0.7.4-beta.2",
9
+ "@saltcorn/base-plugin": "0.7.4",
10
+ "@saltcorn/builder": "0.7.4",
11
+ "@saltcorn/data": "0.7.4",
12
+ "@saltcorn/admin-models": "0.7.4",
13
+ "@saltcorn/markup": "0.7.4",
14
+ "@saltcorn/sbadmin2": "0.7.4",
15
15
  "@socket.io/cluster-adapter": "^0.1.0",
16
16
  "@socket.io/sticky": "^1.0.1",
17
17
  "aws-sdk": "^2.1037.0",
package/public/blockly.js CHANGED
@@ -403,11 +403,10 @@ function activate_blockly({ events, actions, tables }) {
403
403
  Blockly.JavaScript.ORDER_ATOMIC
404
404
  );
405
405
  // TODO: Assemble JavaScript into code variable.
406
- var code = `await fetchJSON(${value_url}, { method: '${dropdown_method}'${
407
- value_body
408
- ? `, body: ${value_body}, headers: { "Content-Type": "application/json" }`
409
- : ""
410
- } })`;
406
+ var code = `await fetchJSON(${value_url}, { method: '${dropdown_method}'${value_body
407
+ ? `, body: ${value_body}, headers: { "Content-Type": "application/json" }`
408
+ : ""
409
+ } })`;
411
410
  // TODO: Change ORDER_NONE to the correct strength.
412
411
  return [code, Blockly.JavaScript.ORDER_NONE];
413
412
  };
@@ -416,6 +415,7 @@ function activate_blockly({ events, actions, tables }) {
416
415
  init: function () {
417
416
  this.appendDummyInput().appendField("Return");
418
417
  this.appendValueInput("GOTO").setCheck("String").appendField("Go to URL");
418
+ this.appendValueInput("POPUP").setCheck("String").appendField("Popup URL");
419
419
  this.appendDummyInput()
420
420
  .appendField("Reload page")
421
421
  .appendField(new Blockly.FieldCheckbox("FALSE"), "RELOAD");
@@ -434,6 +434,11 @@ function activate_blockly({ events, actions, tables }) {
434
434
  "GOTO",
435
435
  Blockly.JavaScript.ORDER_ATOMIC
436
436
  );
437
+ var value_popup = Blockly.JavaScript.valueToCode(
438
+ block,
439
+ "POPUP",
440
+ Blockly.JavaScript.ORDER_ATOMIC
441
+ );
437
442
  var checkbox_reload = block.getFieldValue("RELOAD") == "TRUE";
438
443
  var value_notify = Blockly.JavaScript.valueToCode(
439
444
  block,
@@ -443,6 +448,7 @@ function activate_blockly({ events, actions, tables }) {
443
448
  // TODO: Assemble JavaScript into code variable.
444
449
  let s = "";
445
450
  if (value_goto) s += `goto: ${value_goto},`;
451
+ if (value_popup) s += `popup: ${value_popup},`;
446
452
  if (value_notify) s += `notify: ${value_notify},`;
447
453
  if (checkbox_reload) s += `reload_page: true,`;
448
454
  var code = `return {${s}};\n`;
@@ -4,7 +4,10 @@ function showHideCol(nm, e) {
4
4
  }
5
5
 
6
6
  function lookupIntToString(cell, formatterParams, onRendered) {
7
- const val = `${cell.getValue()}`;
7
+ const cellVal = cell.getValue()
8
+ const val = typeof cellVal === "object" && cellVal !== null
9
+ ? `${cellVal.id}`
10
+ : `${cellVal}`;
8
11
  const res = formatterParams.values[val];
9
12
  return res;
10
13
  }
@@ -150,6 +153,8 @@ function delete_tabulator_row(e, cell) {
150
153
  if (def && def.formatterParams && def.formatterParams.confirm) {
151
154
  if (!confirm("Are you sure you want to delete this row?")) return;
152
155
  }
156
+ const tableName = def?.formatterParams?.tableName || window.tabulator_table_name
157
+
153
158
  const row = cell.getRow().getData();
154
159
  if (!row.id) {
155
160
  cell.getRow().delete();
@@ -157,7 +162,7 @@ function delete_tabulator_row(e, cell) {
157
162
  }
158
163
  $.ajax({
159
164
  type: "DELETE",
160
- url: `/api/${window.tabulator_table_name}/${row.id}`,
165
+ url: `/api/${tableName}/${row.id}`,
161
166
  data: row, // to process primary keys different from id
162
167
  headers: {
163
168
  "CSRF-Token": _sc_globalCsrf,