adminforth 1.2.99 → 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/auth.ts CHANGED
@@ -88,12 +88,13 @@ class AdminForthAuth {
88
88
  console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
89
89
  return null;
90
90
  }
91
- if (pk === null) {
92
- decoded.isRoot = true;
93
- } else {
94
- const dbUser = await this.adminforth.getUserByPk(pk);
95
- decoded.dbUser = dbUser;
91
+ const dbUser = await this.adminforth.getUserByPk(pk);
92
+ if (!dbUser) {
93
+ console.error(`User with pk ${pk} not found in database`);
94
+ // will logout user which was deleted
95
+ return null;
96
96
  }
97
+ decoded.dbUser = dbUser;
97
98
  return decoded;
98
99
  }
99
100
 
@@ -1,5 +1,6 @@
1
1
  import { get } from "http";
2
2
  import { AdminForthResource, IAdminForthDataSourceConnectorBase, AdminForthSortDirections, AdminForthFilterOperators, AdminForthResourceColumn, IAdminForthSort, IAdminForthFilter } from "../types/AdminForthConfig.js";
3
+ import { suggestIfTypo } from "../modules/utils.js";
3
4
 
4
5
 
5
6
  export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
@@ -56,10 +57,21 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
56
57
  throw new Error('Method not implemented.');
57
58
  }
58
59
 
59
- createRecord({ resource, record }: { resource: AdminForthResource; record: any; }): Promise<void> {
60
+ createRecord({ resource, record, adminUser }: {
61
+ resource: AdminForthResource; record: any; adminUser: any;
62
+ }): Promise<void> {
60
63
  // transform value using setFieldValue and call createRecordOriginalValues
61
64
  const newRecord = {...record};
65
+
62
66
  for (const col of resource.dataSourceColumns) {
67
+ if (col.fillOnCreate) {
68
+ if (record[col.name] === undefined) {
69
+ record[col.name] = col.fillOnCreate({
70
+ initialRecord: record, adminUser
71
+ });
72
+ }
73
+ }
74
+
63
75
  newRecord[col.name] = this.setFieldValue(col, record[col.name]);
64
76
  }
65
77
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', newRecord);
@@ -85,10 +97,15 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
85
97
  }): Promise<{ data: any[], total: number }> {
86
98
  if (filters) {
87
99
  filters.map((f) => {
100
+ const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
101
+ if (!fieldObj) {
102
+ const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
103
+ throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
104
+ }
88
105
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
89
- f.value = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
106
+ f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
90
107
  } else {
91
- f.value = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
108
+ f.value = this.setFieldValue(fieldObj, f.value);
92
109
  }
93
110
  });
94
111
  }
@@ -220,6 +220,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
220
220
  sort: { field: string, direction: AdminForthSortDirections }[],
221
221
  filters: { field: string, operator: AdminForthFilterOperators, value: any }[],
222
222
  }): Promise<any[]> {
223
+ console.log('getDataWithOriginalTypes', resource, limit, offset, sort, filters);
223
224
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
224
225
  const tableName = resource.table;
225
226
 
package/dist/auth.js CHANGED
@@ -80,13 +80,13 @@ class AdminForthAuth {
80
80
  console.error(`Invalid token type during verification: ${t}, must be ${mustHaveType}`);
81
81
  return null;
82
82
  }
83
- if (pk === null) {
84
- decoded.isRoot = true;
85
- }
86
- else {
87
- const dbUser = yield this.adminforth.getUserByPk(pk);
88
- decoded.dbUser = dbUser;
83
+ const dbUser = yield this.adminforth.getUserByPk(pk);
84
+ if (!dbUser) {
85
+ console.error(`User with pk ${pk} not found in database`);
86
+ // will logout user which was deleted
87
+ return null;
89
88
  }
89
+ decoded.dbUser = dbUser;
90
90
  return decoded;
91
91
  });
92
92
  }
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { AdminForthFilterOperators } from "../types/AdminForthConfig.js";
11
+ import { suggestIfTypo } from "../modules/utils.js";
11
12
  export default class AdminForthBaseConnector {
12
13
  getPrimaryKey(resource) {
13
14
  for (const col of resource.dataSourceColumns) {
@@ -49,10 +50,17 @@ export default class AdminForthBaseConnector {
49
50
  createRecordOriginalValues({ resource, record }) {
50
51
  throw new Error('Method not implemented.');
51
52
  }
52
- createRecord({ resource, record }) {
53
+ createRecord({ resource, record, adminUser }) {
53
54
  // transform value using setFieldValue and call createRecordOriginalValues
54
55
  const newRecord = Object.assign({}, record);
55
56
  for (const col of resource.dataSourceColumns) {
57
+ if (col.fillOnCreate) {
58
+ if (record[col.name] === undefined) {
59
+ record[col.name] = col.fillOnCreate({
60
+ initialRecord: record, adminUser
61
+ });
62
+ }
63
+ }
56
64
  newRecord[col.name] = this.setFieldValue(col, record[col.name]);
57
65
  }
58
66
  process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record', newRecord);
@@ -68,11 +76,16 @@ export default class AdminForthBaseConnector {
68
76
  return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters, getTotals }) {
69
77
  if (filters) {
70
78
  filters.map((f) => {
79
+ const fieldObj = resource.dataSourceColumns.find((col) => col.name == f.field);
80
+ if (!fieldObj) {
81
+ const similar = suggestIfTypo(resource.dataSourceColumns.map((col) => col.name), f.field);
82
+ throw new Error(`Field '${f.field}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
83
+ }
71
84
  if (f.operator == AdminForthFilterOperators.IN || f.operator == AdminForthFilterOperators.NIN) {
72
- f.value = f.value.map((val) => this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), val));
85
+ f.value = f.value.map((val) => this.setFieldValue(fieldObj, val));
73
86
  }
74
87
  else {
75
- f.value = this.setFieldValue(resource.dataSourceColumns.find((col) => col.name == f.field), f.value);
88
+ f.value = this.setFieldValue(fieldObj, f.value);
76
89
  }
77
90
  });
78
91
  }
@@ -217,6 +217,7 @@ class ClickhouseConnector extends AdminForthBaseConnector {
217
217
  }
218
218
  getDataWithOriginalTypes(_a) {
219
219
  return __awaiter(this, arguments, void 0, function* ({ resource, limit, offset, sort, filters }) {
220
+ console.log('getDataWithOriginalTypes', resource, limit, offset, sort, filters);
220
221
  const columns = resource.dataSourceColumns.map((col) => col.name).join(', ');
221
222
  const tableName = resource.table;
222
223
  const where = this.whereClause(resource, filters);
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);
@@ -207,7 +211,21 @@ class AdminForth {
207
211
  });
208
212
  }
209
213
  resource(resourceId) {
210
- return this.config.resources.find((res) => res.resourceId === 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];
211
229
  }
212
230
  setupEndpoints(server) {
213
231
  this.restApi.registerEndpoints(server);
@@ -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 = [];
@@ -7,42 +7,26 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- // export interface IOperationalResource {
11
- // get: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<any[]>;
12
- // list: (filters: IAdminForthFilter | IAdminForthFilter[], limit: number, offset: number, sort: IAdminForthSort | IAdminForthSort[]) => Promise<any[]>;
13
- // count: (filters: IAdminForthFilter | IAdminForthFilter[]) => Promise<number>;
14
- // create: (record: any) => Promise<any>;
15
- // update: (primaryKey: any, record: any) => Promise<any>;
16
- // delete: (primaryKey: any) => Promise<boolean>;
17
- // deleteMany: (primaryKeys: any[]) => Promise<boolean>;
18
- // }
19
- // async getData({ resource, limit, offset, sort, filters }: {
20
- // resource: AdminForthResource,
21
- // limit: number,
22
- // offset: number,
23
- // sort: { field: string, direction: AdminForthSortDirections }[],
24
- // filters: { field: string, operator: AdminForthFilterOperators, value: any }[]
25
- // }): Promise<{ data: any[], total: number }> {
26
10
  function filtersIfFilter(filter) {
27
- return (typeof filter === 'object' ? [filter] : filter);
11
+ return (Array.isArray(filter) ? filter : [filter]);
28
12
  }
29
13
  function sortsIfSort(sort) {
30
- return (typeof sort === 'object' ? [sort] : sort);
14
+ return (Array.isArray(sort) ? sort : [sort]);
31
15
  }
32
- export class OperationalResource {
16
+ export default class OperationalResource {
33
17
  constructor(dataConnector, resourceConfig) {
34
18
  this.dataConnector = dataConnector;
35
19
  this.resourceConfig = resourceConfig;
36
20
  }
37
21
  get(filter) {
38
22
  return __awaiter(this, void 0, void 0, function* () {
39
- return yield this.dataConnector.getData({
23
+ return (yield this.dataConnector.getData({
40
24
  resource: this.resourceConfig,
41
25
  filters: filtersIfFilter(filter),
42
26
  limit: 1,
43
27
  offset: 0,
44
28
  sort: [],
45
- })[0];
29
+ })).data[0] || null;
46
30
  });
47
31
  }
48
32
  list(filter, limit, offset, sort) {
@@ -68,7 +52,7 @@ export class OperationalResource {
68
52
  }
69
53
  create(record) {
70
54
  return __awaiter(this, void 0, void 0, function* () {
71
- return yield this.dataConnector.createRecord({ resource: this.resourceConfig, record });
55
+ return yield this.dataConnector.createRecord({ resource: this.resourceConfig, record, adminUser: null });
72
56
  });
73
57
  }
74
58
  update(primaryKey, record) {