adminforth 1.2.98 → 1.3.1

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/dist/index.js CHANGED
@@ -19,16 +19,18 @@ import PostgresConnector from './dataConnectors/postgres.js';
19
19
  import SQLiteConnector from './dataConnectors/sqlite.js';
20
20
  import CodeInjector from './modules/codeInjector.js';
21
21
  import ExpressServer from './servers/express.js';
22
- import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
22
+ import { ADMINFORTH_VERSION, listify, suggestIfTypo } from './modules/utils.js';
23
23
  import { AdminForthFilterOperators, AdminForthDataTypes, AdminForthResourcePages, } from './types/AdminForthConfig.js';
24
24
  import AdminForthPlugin from './basePlugin.js';
25
25
  import ConfigValidator from './modules/configValidator.js';
26
26
  import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
27
27
  import ClickhouseConnector from './dataConnectors/clickhouse.js';
28
+ import OperationalResource from './modules/operationalResource.js';
28
29
  // exports
29
30
  export * from './types/AdminForthConfig.js';
30
31
  export { interpretResource };
31
32
  export { AdminForthPlugin };
33
+ export { suggestIfTypo };
32
34
  class AdminForth {
33
35
  constructor(config) {
34
36
  _AdminForth_defaultConfig.set(this, {
@@ -85,20 +87,22 @@ class AdminForth {
85
87
  this.config.dataSources.forEach((ds) => {
86
88
  const dbType = ds.url.split(':')[0];
87
89
  if (!this.config.databaseConnectors[dbType]) {
88
- throw new Error(`Database type ${dbType} is not supported, consider using databaseConnectors in AdminForth config`);
90
+ throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
89
91
  }
90
92
  this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({ url: ds.url });
91
93
  });
92
94
  yield Promise.all(this.config.resources.map((res) => __awaiter(this, void 0, void 0, function* () {
93
95
  if (!this.connectors[res.dataSource]) {
94
- throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}'`);
96
+ const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
97
+ throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
98
+ ? `. Did you mean '${similar}'?` : 'Available dataSources: ' + Object.keys(this.connectors).join(', ')}`);
95
99
  }
96
100
  const fieldTypes = yield this.connectors[res.dataSource].discoverFields(res);
97
101
  if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
98
102
  throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
99
103
  }
100
104
  if (fieldTypes === null) {
101
- console.error(`DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
105
+ console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
102
106
  return;
103
107
  }
104
108
  if (!res.columns) {
@@ -106,7 +110,8 @@ class AdminForth {
106
110
  }
107
111
  res.columns.forEach((col, i) => {
108
112
  if (!fieldTypes[col.name] && !col.virtual) {
109
- throw new Error(`Resource '${res.table}' has no column '${col.name}'`);
113
+ const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
114
+ throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
110
115
  }
111
116
  // first find discovered values, but allow override
112
117
  res.columns[i] = Object.assign(Object.assign({}, fieldTypes[col.name]), col);
@@ -118,6 +123,10 @@ class AdminForth {
118
123
  }
119
124
  })));
120
125
  this.statuses.dbDiscover = 'done';
126
+ this.operationalResources = {};
127
+ this.config.resources.forEach((resource) => {
128
+ this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
129
+ });
121
130
  // console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
122
131
  });
123
132
  }
@@ -128,9 +137,11 @@ class AdminForth {
128
137
  }
129
138
  getUserByPk(pk) {
130
139
  return __awaiter(this, void 0, void 0, function* () {
131
- const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
140
+ const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
132
141
  if (!resource) {
133
- throw new Error('No auth resource found');
142
+ const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
143
+ throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
144
+ `Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`);
134
145
  }
135
146
  const users = yield this.connectors[resource.dataSource].getData({
136
147
  resource,
@@ -148,13 +159,6 @@ class AdminForth {
148
159
  return __awaiter(this, arguments, void 0, function* ({ resource, record, adminUser }) {
149
160
  var _c, _d, _e, _f, _g;
150
161
  for (const column of resource.columns) {
151
- if (column.fillOnCreate) {
152
- if (record[column.name] === undefined) {
153
- record[column.name] = column.fillOnCreate({
154
- initialRecord: record, adminUser
155
- });
156
- }
157
- }
158
162
  if (((_c = column.required) === null || _c === void 0 ? void 0 : _c.create) &&
159
163
  record[column.name] === undefined &&
160
164
  column.showIn.includes(AdminForthResourcePages.create)) {
@@ -191,7 +195,7 @@ class AdminForth {
191
195
  }
192
196
  const connector = this.connectors[resource.dataSource];
193
197
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
194
- yield connector.createRecord({ resource, record });
198
+ yield connector.createRecord({ resource, record, adminUser });
195
199
  // execute hook if needed
196
200
  for (const hook of listify((_g = (_f = resource.hooks) === null || _f === void 0 ? void 0 : _f.create) === null || _g === void 0 ? void 0 : _g.afterSave)) {
197
201
  console.log('Hook afterSave', hook);
@@ -206,6 +210,23 @@ class AdminForth {
206
210
  return { ok: true };
207
211
  });
208
212
  }
213
+ resource(resourceId) {
214
+ if (this.statuses.dbDiscover !== 'done') {
215
+ if (this.statuses.dbDiscover === 'running') {
216
+ throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n' +
217
+ 'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
218
+ }
219
+ else {
220
+ throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n' +
221
+ 'Call admin.discoverDatabases() first and await it before using data API');
222
+ }
223
+ }
224
+ if (!this.operationalResources[resourceId]) {
225
+ const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
226
+ throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
227
+ }
228
+ return this.operationalResources[resourceId];
229
+ }
209
230
  setupEndpoints(server) {
210
231
  this.restApi.registerEndpoints(server);
211
232
  }
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { AdminForthResourcePages, AllowedActionsEnum, } from "../types/AdminForthConfig.js";
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
- import { guessLabelFromName } from './utils.js';
13
+ import { guessLabelFromName, suggestIfTypo } from './utils.js';
14
14
  import crypto from 'crypto';
15
15
  export default class ConfigValidator {
16
16
  constructor(adminforth, config) {
@@ -47,15 +47,6 @@ export default class ConfigValidator {
47
47
  validateConfig() {
48
48
  var _a;
49
49
  const errors = [];
50
- if (this.config.rootUser) {
51
- if (!this.config.rootUser.username) {
52
- throw new Error('rootUser.username is required');
53
- }
54
- if (!this.config.rootUser.password) {
55
- throw new Error('rootUser.password is required');
56
- }
57
- console.log('\n ☝️☝️☝️ [INSECURE ALERT] config.rootUser is set, please create a new user to login in backoffice and remove config.rootUser from config ASAP when you are in production\n');
58
- }
59
50
  if (!this.config.customization.customComponentsDir) {
60
51
  this.config.customization.customComponentsDir = './custom';
61
52
  }
@@ -67,8 +58,12 @@ export default class ConfigValidator {
67
58
  this.config.customization.customComponentsDir = undefined;
68
59
  }
69
60
  if (this.config.auth) {
70
- if (!this.config.auth.resourceId) {
71
- throw new Error('No config.auth.resourceId defined');
61
+ // TODO: remove in future releases
62
+ if (!this.config.auth.usersResourceId && this.config.auth.resourceId) {
63
+ this.config.auth.usersResourceId = this.config.auth.resourceId;
64
+ }
65
+ if (!this.config.auth.usersResourceId) {
66
+ throw new Error('No config.auth.usersResourceId defined');
72
67
  }
73
68
  if (!this.config.auth.passwordHashField) {
74
69
  throw new Error('No config.auth.passwordHashField defined');
@@ -79,9 +74,10 @@ export default class ConfigValidator {
79
74
  if (this.config.auth.loginBackgroundImage) {
80
75
  errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
81
76
  }
82
- const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.resourceId);
77
+ const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
83
78
  if (!userResource) {
84
- throw new Error(`Resource with id "${this.config.auth.resourceId}" not found`);
79
+ const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId || res.table), this.config.auth.usersResourceId);
80
+ throw new Error(`Resource with id "${this.config.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
85
81
  }
86
82
  if (!this.config.auth.beforeLoginConfirmation) {
87
83
  this.config.auth.beforeLoginConfirmation = [];
@@ -0,0 +1,72 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ function filtersIfFilter(filter) {
11
+ return (Array.isArray(filter) ? filter : [filter]);
12
+ }
13
+ function sortsIfSort(sort) {
14
+ return (Array.isArray(sort) ? sort : [sort]);
15
+ }
16
+ export default class OperationalResource {
17
+ constructor(dataConnector, resourceConfig) {
18
+ this.dataConnector = dataConnector;
19
+ this.resourceConfig = resourceConfig;
20
+ }
21
+ get(filter) {
22
+ return __awaiter(this, void 0, void 0, function* () {
23
+ return (yield this.dataConnector.getData({
24
+ resource: this.resourceConfig,
25
+ filters: filtersIfFilter(filter),
26
+ limit: 1,
27
+ offset: 0,
28
+ sort: [],
29
+ })).data[0] || null;
30
+ });
31
+ }
32
+ list(filter, limit, offset, sort) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ const { data } = yield this.dataConnector.getData({
35
+ resource: this.resourceConfig,
36
+ filters: filtersIfFilter(filter),
37
+ limit,
38
+ offset,
39
+ sort: sortsIfSort(sort),
40
+ getTotals: false,
41
+ });
42
+ return data;
43
+ });
44
+ }
45
+ count(filter) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ return yield this.dataConnector.getCount({
48
+ resource: this.resourceConfig,
49
+ filters: filtersIfFilter(filter),
50
+ });
51
+ });
52
+ }
53
+ create(record) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ return yield this.dataConnector.createRecord({ resource: this.resourceConfig, record, adminUser: null });
56
+ });
57
+ }
58
+ update(primaryKey, record) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ return yield this.dataConnector.updateRecord({
61
+ resource: this.resourceConfig,
62
+ recordId: primaryKey,
63
+ newValues: record
64
+ });
65
+ });
66
+ }
67
+ delete(primaryKey) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ return yield this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
70
+ });
71
+ }
72
+ }
@@ -47,67 +47,58 @@ export default class AdminForthRestAPI {
47
47
  const { username, password } = body;
48
48
  let adminUser;
49
49
  let toReturn = { ok: true, allowedLogin: true };
50
- let token;
51
- if (this.adminforth.config.rootUser
52
- && username === this.adminforth.config.rootUser.username
53
- && password === this.adminforth.config.rootUser.password) {
54
- this.adminforth.auth.setAuthCookie({ response, username, pk: null });
55
- adminUser = { isRoot: true, dbUser: null, pk: null, username: this.adminforth.config.rootUser.username };
56
- }
57
- else {
58
- // get resource from db
59
- if (!this.adminforth.config.auth) {
60
- throw new Error('No config.auth defined');
61
- }
62
- const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.resourceId);
63
- // if there is no passwordHashField, in columns, add it, with backendOnly and showIn: []
64
- if (!userResource.dataSourceColumns.find((col) => col.name === this.adminforth.config.auth.passwordHashField)) {
65
- userResource.dataSourceColumns.push({
66
- name: this.adminforth.config.auth.passwordHashField,
67
- backendOnly: true,
68
- showIn: [],
69
- type: AdminForthDataTypes.STRING,
70
- });
71
- console.log('Adding passwordHashField to userResource', userResource);
72
- }
73
- const userRecord = (_b = (yield this.adminforth.connectors[userResource.dataSource].getData({
74
- resource: userResource,
75
- filters: [
76
- { field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
77
- ],
78
- limit: 1,
79
- offset: 0,
80
- sort: [],
81
- })).data) === null || _b === void 0 ? void 0 : _b[0];
82
- if (!userRecord) {
83
- return { error: 'User not found' };
84
- }
85
- const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
86
- const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
87
- if (valid) {
88
- adminUser = {
89
- isRoot: false, dbUser: userRecord,
90
- pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
91
- username,
92
- };
93
- const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
94
- if (beforeLoginConfirmation === null || beforeLoginConfirmation === void 0 ? void 0 : beforeLoginConfirmation.length) {
95
- for (const hook of beforeLoginConfirmation) {
96
- const resp = yield hook({ adminUser, response });
97
- if ((_c = resp === null || resp === void 0 ? void 0 : resp.body) === null || _c === void 0 ? void 0 : _c.redirectTo) {
98
- toReturn = { ok: resp.ok, redirectTo: (_d = resp === null || resp === void 0 ? void 0 : resp.body) === null || _d === void 0 ? void 0 : _d.redirectTo, allowedLogin: (_e = resp === null || resp === void 0 ? void 0 : resp.body) === null || _e === void 0 ? void 0 : _e.allowedLogin };
99
- break;
100
- }
50
+ // get resource from db
51
+ if (!this.adminforth.config.auth) {
52
+ throw new Error('No config.auth defined we need it to find user, please follow the docs');
53
+ }
54
+ const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
55
+ // if there is no passwordHashField, in columns, add it, with backendOnly and showIn: []
56
+ if (!userResource.dataSourceColumns.find((col) => col.name === this.adminforth.config.auth.passwordHashField)) {
57
+ userResource.dataSourceColumns.push({
58
+ name: this.adminforth.config.auth.passwordHashField,
59
+ backendOnly: true,
60
+ showIn: [],
61
+ type: AdminForthDataTypes.STRING,
62
+ });
63
+ console.log('Adding passwordHashField to userResource', userResource);
64
+ }
65
+ const userRecord = (_b = (yield this.adminforth.connectors[userResource.dataSource].getData({
66
+ resource: userResource,
67
+ filters: [
68
+ { field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
69
+ ],
70
+ limit: 1,
71
+ offset: 0,
72
+ sort: [],
73
+ })).data) === null || _b === void 0 ? void 0 : _b[0];
74
+ if (!userRecord) {
75
+ return { error: 'User not found' };
76
+ }
77
+ const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
78
+ const valid = yield AdminForthAuth.verifyPassword(password, passwordHash);
79
+ if (valid) {
80
+ adminUser = {
81
+ dbUser: userRecord,
82
+ pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
83
+ username,
84
+ };
85
+ const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
86
+ if (beforeLoginConfirmation === null || beforeLoginConfirmation === void 0 ? void 0 : beforeLoginConfirmation.length) {
87
+ for (const hook of beforeLoginConfirmation) {
88
+ const resp = yield hook({ adminUser, response });
89
+ if ((_c = resp === null || resp === void 0 ? void 0 : resp.body) === null || _c === void 0 ? void 0 : _c.redirectTo) {
90
+ toReturn = { ok: resp.ok, redirectTo: (_d = resp === null || resp === void 0 ? void 0 : resp.body) === null || _d === void 0 ? void 0 : _d.redirectTo, allowedLogin: (_e = resp === null || resp === void 0 ? void 0 : resp.body) === null || _e === void 0 ? void 0 : _e.allowedLogin };
91
+ break;
101
92
  }
102
93
  }
103
- if (toReturn.allowedLogin) {
104
- this.adminforth.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
105
- }
106
94
  }
107
- else {
108
- return { error: INVALID_MESSAGE };
95
+ if (toReturn.allowedLogin) {
96
+ this.adminforth.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
109
97
  }
110
98
  }
99
+ else {
100
+ return { error: INVALID_MESSAGE };
101
+ }
111
102
  return toReturn;
112
103
  })
113
104
  });
@@ -138,7 +129,7 @@ export default class AdminForthRestAPI {
138
129
  throw new Error('No config.auth defined');
139
130
  }
140
131
  const usernameField = this.adminforth.config.auth.usernameField;
141
- const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.resourceId);
132
+ const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
142
133
  const usernameColumn = resource.columns.find((col) => col.name === usernameField);
143
134
  return {
144
135
  brandName: this.adminforth.config.customization.brandName,
@@ -154,18 +145,12 @@ export default class AdminForthRestAPI {
154
145
  method: 'GET',
155
146
  path: '/get_base_config',
156
147
  handler: (_k) => __awaiter(this, [_k], void 0, function* ({ input, adminUser, cookies }) {
157
- var _l, _m, _o, _p, _q;
148
+ var _l, _m, _o, _p;
158
149
  let username = '';
159
150
  let userFullName = '';
160
- if (adminUser.isRoot) {
161
- // isRoot can be in JWT token still when rootUser deleted, so we check for rootUser?"
162
- username = ((_l = this.adminforth.config.rootUser) === null || _l === void 0 ? void 0 : _l.username) || 'RootUser';
163
- }
164
- else {
165
- const dbUser = adminUser.dbUser;
166
- username = dbUser[this.adminforth.config.auth.usernameField];
167
- userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
168
- }
151
+ const dbUser = adminUser.dbUser;
152
+ username = dbUser[this.adminforth.config.auth.usernameField];
153
+ userFullName = dbUser[this.adminforth.config.auth.userFullNameField];
169
154
  const userData = {
170
155
  [this.adminforth.config.auth.usernameField]: username,
171
156
  [this.adminforth.config.auth.userFullNameField]: userFullName
@@ -213,7 +198,7 @@ export default class AdminForthRestAPI {
213
198
  yield processMenuItem(newMenuItem);
214
199
  newMenu.push(newMenuItem);
215
200
  }
216
- const announcementBadge = (_o = (_m = this.adminforth.config.customization).announcementBadge) === null || _o === void 0 ? void 0 : _o.call(_m, adminUser);
201
+ const announcementBadge = (_m = (_l = this.adminforth.config.customization).announcementBadge) === null || _m === void 0 ? void 0 : _m.call(_l, adminUser);
217
202
  return {
218
203
  user: userData,
219
204
  resources: this.adminforth.config.resources.map((res) => ({
@@ -228,8 +213,8 @@ export default class AdminForthRestAPI {
228
213
  deleteConfirmation: this.adminforth.config.deleteConfirmation,
229
214
  auth: this.adminforth.config.auth,
230
215
  usernameField: this.adminforth.config.auth.usernameField,
231
- title: (_p = this.adminforth.config.customization) === null || _p === void 0 ? void 0 : _p.title,
232
- emptyFieldPlaceholder: (_q = this.adminforth.config.customization) === null || _q === void 0 ? void 0 : _q.emptyFieldPlaceholder,
216
+ title: (_o = this.adminforth.config.customization) === null || _o === void 0 ? void 0 : _o.title,
217
+ emptyFieldPlaceholder: (_p = this.adminforth.config.customization) === null || _p === void 0 ? void 0 : _p.emptyFieldPlaceholder,
233
218
  announcementBadge,
234
219
  },
235
220
  adminUser,
@@ -247,7 +232,7 @@ export default class AdminForthRestAPI {
247
232
  server.endpoint({
248
233
  method: 'POST',
249
234
  path: '/get_resource',
250
- handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
235
+ handler: (_q) => __awaiter(this, [_q], void 0, function* ({ body, adminUser }) {
251
236
  const { resourceId } = body;
252
237
  if (!this.adminforth.statuses.dbDiscover) {
253
238
  return { error: 'Database discovery not started' };
@@ -278,8 +263,8 @@ export default class AdminForthRestAPI {
278
263
  server.endpoint({
279
264
  method: 'POST',
280
265
  path: '/get_resource_data',
281
- handler: (_s) => __awaiter(this, [_s], void 0, function* ({ body, adminUser }) {
282
- var _t, _u, _v, _w;
266
+ handler: (_r) => __awaiter(this, [_r], void 0, function* ({ body, adminUser }) {
267
+ var _s, _t, _u, _v;
283
268
  const { resourceId, source } = body;
284
269
  if (['show', 'list'].includes(source) === false) {
285
270
  return { error: 'Invalid source, should be list or show' };
@@ -299,7 +284,7 @@ export default class AdminForthRestAPI {
299
284
  if (!allowed) {
300
285
  return { error };
301
286
  }
302
- for (const hook of listify((_u = (_t = resource.hooks) === null || _t === void 0 ? void 0 : _t[source]) === null || _u === void 0 ? void 0 : _u.beforeDatasourceRequest)) {
287
+ for (const hook of listify((_t = (_s = resource.hooks) === null || _s === void 0 ? void 0 : _s[source]) === null || _t === void 0 ? void 0 : _t.beforeDatasourceRequest)) {
303
288
  const resp = yield hook({ resource, query: body, adminUser });
304
289
  if (!resp || (!resp.ok && !resp.error)) {
305
290
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -378,7 +363,7 @@ export default class AdminForthRestAPI {
378
363
  item._label = resource.recordLabel(item);
379
364
  });
380
365
  // only after adminforth made all post processing, give user ability to edit it
381
- for (const hook of listify((_w = (_v = resource.hooks) === null || _v === void 0 ? void 0 : _v[source]) === null || _w === void 0 ? void 0 : _w.afterDatasourceResponse)) {
366
+ for (const hook of listify((_v = (_u = resource.hooks) === null || _u === void 0 ? void 0 : _u[source]) === null || _v === void 0 ? void 0 : _v.afterDatasourceResponse)) {
382
367
  const resp = yield hook({ resource, response: data.data, adminUser });
383
368
  if (!resp || (!resp.ok && !resp.error)) {
384
369
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -393,8 +378,8 @@ export default class AdminForthRestAPI {
393
378
  server.endpoint({
394
379
  method: 'POST',
395
380
  path: '/get_resource_foreign_data',
396
- handler: (_x) => __awaiter(this, [_x], void 0, function* ({ body, adminUser }) {
397
- var _y, _z, _0, _1;
381
+ handler: (_w) => __awaiter(this, [_w], void 0, function* ({ body, adminUser }) {
382
+ var _x, _y, _z, _0;
398
383
  const { resourceId, column } = body;
399
384
  if (!this.adminforth.statuses.dbDiscover) {
400
385
  return { error: 'Database discovery not started' };
@@ -415,7 +400,7 @@ export default class AdminForthRestAPI {
415
400
  }
416
401
  const targetResourceId = columnConfig.foreignResource.resourceId;
417
402
  const targetResource = this.adminforth.config.resources.find((res) => res.resourceId == targetResourceId);
418
- for (const hook of listify((_z = (_y = columnConfig.foreignResource.hooks) === null || _y === void 0 ? void 0 : _y.dropdownList) === null || _z === void 0 ? void 0 : _z.beforeDatasourceRequest)) {
403
+ for (const hook of listify((_y = (_x = columnConfig.foreignResource.hooks) === null || _x === void 0 ? void 0 : _x.dropdownList) === null || _y === void 0 ? void 0 : _y.beforeDatasourceRequest)) {
419
404
  const resp = yield hook({ query: body, adminUser, resource: targetResource });
420
405
  if (!resp || (!resp.ok && !resp.error)) {
421
406
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -444,7 +429,7 @@ export default class AdminForthRestAPI {
444
429
  const response = {
445
430
  items
446
431
  };
447
- for (const hook of listify((_1 = (_0 = columnConfig.foreignResource.hooks) === null || _0 === void 0 ? void 0 : _0.dropdownList) === null || _1 === void 0 ? void 0 : _1.afterDatasourceResponse)) {
432
+ for (const hook of listify((_0 = (_z = columnConfig.foreignResource.hooks) === null || _z === void 0 ? void 0 : _z.dropdownList) === null || _0 === void 0 ? void 0 : _0.afterDatasourceResponse)) {
448
433
  const resp = yield hook({ response, adminUser, resource: targetResource });
449
434
  if (!resp || (!resp.ok && !resp.error)) {
450
435
  throw new Error(`Hook must return object with {ok: true} or { error: 'Error' } `);
@@ -459,7 +444,7 @@ export default class AdminForthRestAPI {
459
444
  server.endpoint({
460
445
  method: 'POST',
461
446
  path: '/get_min_max_for_columns',
462
- handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body }) {
447
+ handler: (_1) => __awaiter(this, [_1], void 0, function* ({ body }) {
463
448
  const { resourceId } = body;
464
449
  if (!this.adminforth.statuses.dbDiscover) {
465
450
  return { error: 'Database discovery not started' };
@@ -488,7 +473,7 @@ export default class AdminForthRestAPI {
488
473
  server.endpoint({
489
474
  method: 'POST',
490
475
  path: '/create_record',
491
- handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
476
+ handler: (_2) => __awaiter(this, [_2], void 0, function* ({ body, adminUser }) {
492
477
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
493
478
  if (!resource) {
494
479
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -512,8 +497,8 @@ export default class AdminForthRestAPI {
512
497
  server.endpoint({
513
498
  method: 'POST',
514
499
  path: '/update_record',
515
- handler: (_4) => __awaiter(this, [_4], void 0, function* ({ body, adminUser }) {
516
- var _5, _6, _7, _8;
500
+ handler: (_3) => __awaiter(this, [_3], void 0, function* ({ body, adminUser }) {
501
+ var _4, _5, _6, _7;
517
502
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
518
503
  if (!resource) {
519
504
  return { error: `Resource '${body['resourceId']}' not found` };
@@ -532,7 +517,7 @@ export default class AdminForthRestAPI {
532
517
  return { error };
533
518
  }
534
519
  // execute hook if needed
535
- for (const hook of listify((_6 = (_5 = resource.hooks) === null || _5 === void 0 ? void 0 : _5.edit) === null || _6 === void 0 ? void 0 : _6.beforeSave)) {
520
+ for (const hook of listify((_5 = (_4 = resource.hooks) === null || _4 === void 0 ? void 0 : _4.edit) === null || _5 === void 0 ? void 0 : _5.beforeSave)) {
536
521
  const resp = yield hook({ resource, record, adminUser });
537
522
  if (!resp || (!resp.ok && !resp.error)) {
538
523
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -559,7 +544,7 @@ export default class AdminForthRestAPI {
559
544
  yield connector.updateRecord({ resource, recordId, newValues });
560
545
  }
561
546
  // execute hook if needed
562
- for (const hook of listify((_8 = (_7 = resource.hooks) === null || _7 === void 0 ? void 0 : _7.edit) === null || _8 === void 0 ? void 0 : _8.afterSave)) {
547
+ for (const hook of listify((_7 = (_6 = resource.hooks) === null || _6 === void 0 ? void 0 : _6.edit) === null || _7 === void 0 ? void 0 : _7.afterSave)) {
563
548
  const resp = yield hook({ resource, record, adminUser, oldRecord });
564
549
  if (!resp || (!resp.ok && !resp.error)) {
565
550
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -576,8 +561,8 @@ export default class AdminForthRestAPI {
576
561
  server.endpoint({
577
562
  method: 'POST',
578
563
  path: '/delete_record',
579
- handler: (_9) => __awaiter(this, [_9], void 0, function* ({ body, adminUser }) {
580
- var _10, _11, _12, _13;
564
+ handler: (_8) => __awaiter(this, [_8], void 0, function* ({ body, adminUser }) {
565
+ var _9, _10, _11, _12;
581
566
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == body['resourceId']);
582
567
  const record = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, body['primaryKey']);
583
568
  if (!resource) {
@@ -595,7 +580,7 @@ export default class AdminForthRestAPI {
595
580
  return { error };
596
581
  }
597
582
  // execute hook if needed
598
- for (const hook of listify((_11 = (_10 = resource.hooks) === null || _10 === void 0 ? void 0 : _10.delete) === null || _11 === void 0 ? void 0 : _11.beforeSave)) {
583
+ for (const hook of listify((_10 = (_9 = resource.hooks) === null || _9 === void 0 ? void 0 : _9.delete) === null || _10 === void 0 ? void 0 : _10.beforeSave)) {
599
584
  const resp = yield hook({ resource, record, adminUser });
600
585
  if (!resp || (!resp.ok && !resp.error)) {
601
586
  throw new Error(`Hook beforeSave must return object with {ok: true} or { error: 'Error' } `);
@@ -607,7 +592,7 @@ export default class AdminForthRestAPI {
607
592
  const connector = this.adminforth.connectors[resource.dataSource];
608
593
  yield connector.deleteRecord({ resource, recordId: body['primaryKey'] });
609
594
  // execute hook if needed
610
- for (const hook of listify((_13 = (_12 = resource.hooks) === null || _12 === void 0 ? void 0 : _12.delete) === null || _13 === void 0 ? void 0 : _13.afterSave)) {
595
+ for (const hook of listify((_12 = (_11 = resource.hooks) === null || _11 === void 0 ? void 0 : _11.delete) === null || _12 === void 0 ? void 0 : _12.afterSave)) {
611
596
  const resp = yield hook({ resource, record, adminUser });
612
597
  if (!resp || (!resp.ok && !resp.error)) {
613
598
  throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
@@ -624,7 +609,7 @@ export default class AdminForthRestAPI {
624
609
  server.endpoint({
625
610
  method: 'POST',
626
611
  path: '/start_bulk_action',
627
- handler: (_14) => __awaiter(this, [_14], void 0, function* ({ body, adminUser }) {
612
+ handler: (_13) => __awaiter(this, [_13], void 0, function* ({ body, adminUser }) {
628
613
  const { resourceId, actionId, recordIds } = body;
629
614
  const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
630
615
  if (!resource) {
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import fs from 'fs';
4
+ import Fuse from 'fuse.js';
4
5
  // @ts-ignore-next-line
5
6
  const csscolors = {
6
7
  "aliceblue": "#f0f8ff",
@@ -307,3 +308,20 @@ export function inverseRGBA(rgba) {
307
308
  let brightness = (r * 299 + g * 587 + b * 114) / 1000;
308
309
  return brightness > 128 ? 'rgba(0,0,0,1)' : 'rgba(255,255,255,1)';
309
310
  }
311
+ export function suggestIfTypo(names, name) {
312
+ console.log('names', names);
313
+ console.log('name', name);
314
+ if (!name) {
315
+ return null;
316
+ }
317
+ const options = {
318
+ includeScore: true, // Includes score in the results to see how close matches are
319
+ threshold: 0.3, // Defines the fuzziness (lower values mean stricter matches)
320
+ };
321
+ const fuse = new Fuse(names.filter((n) => !!n), options);
322
+ // Search for a resource
323
+ const result = fuse.search(name);
324
+ if (result.length > 0) {
325
+ return result[0].item;
326
+ }
327
+ }