@adminforth/import-export 1.4.25 → 1.4.26
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/build.log +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +72 -14
- package/index.ts +75 -15
- package/package.json +5 -2
- package/pnpm-workspace.yaml +4 -0
package/build.log
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
7
7
|
authResourceId: string;
|
|
8
8
|
adminforth: IAdminForth;
|
|
9
9
|
constructor(options: PluginOptions);
|
|
10
|
+
private isRowValid;
|
|
10
11
|
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource): Promise<void>;
|
|
11
12
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource): void;
|
|
12
13
|
instanceUniqueRepresentation(pluginOptions: any): string;
|
package/dist/index.js
CHANGED
|
@@ -7,12 +7,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthPlugin, suggestIfTypo, AdminForthFilterOperators, Filters, AdminForthDataTypes } from "adminforth";
|
|
10
|
+
import { AdminForthPlugin, suggestIfTypo, AdminForthFilterOperators, Filters, AdminForthDataTypes, rejectApiRawFilters } from "adminforth";
|
|
11
|
+
import pLimit from 'p-limit';
|
|
11
12
|
export default class ImportExport extends AdminForthPlugin {
|
|
12
13
|
constructor(options) {
|
|
13
14
|
super(options, import.meta.url);
|
|
14
15
|
this.options = options;
|
|
15
16
|
}
|
|
17
|
+
isRowValid(row) {
|
|
18
|
+
let errors = [];
|
|
19
|
+
for (const col of Object.keys(row)) {
|
|
20
|
+
const resourceCol = this.resourceConfig.columns.find(c => c.name === col);
|
|
21
|
+
if (!resourceCol) {
|
|
22
|
+
errors.push(`Column '${col}' not found in resource configuration.`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (resourceCol.backendOnly) {
|
|
26
|
+
errors.push(`Column '${col}' is backend only and cannot be imported.`);
|
|
27
|
+
}
|
|
28
|
+
if (resourceCol.enum && !resourceCol.enum.some(e => e.value === row[col])) {
|
|
29
|
+
errors.push(`Column '${col}' has an enum of [${resourceCol.enum.map(e => e.label).join(', ')}] but got value '${row[col]}'.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
16
34
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
17
35
|
const _super = Object.create(null, {
|
|
18
36
|
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
@@ -55,6 +73,10 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
55
73
|
path: `/plugin/${this.pluginInstanceId}/export-csv`,
|
|
56
74
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
57
75
|
const { filters, sort } = body;
|
|
76
|
+
const rawFilterError = rejectApiRawFilters(body.filters);
|
|
77
|
+
if (rawFilterError) {
|
|
78
|
+
return rawFilterError;
|
|
79
|
+
}
|
|
58
80
|
const data = yield this.adminforth.connectors[this.resourceConfig.dataSource].getData({
|
|
59
81
|
resource: this.resourceConfig,
|
|
60
82
|
limit: 1e6,
|
|
@@ -64,7 +86,7 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
64
86
|
getTotals: true,
|
|
65
87
|
});
|
|
66
88
|
// prepare data for PapaParse unparse
|
|
67
|
-
const columns = this.resourceConfig.columns.filter((col) => !col.virtual);
|
|
89
|
+
const columns = this.resourceConfig.columns.filter((col) => !col.virtual && !col.backendOnly);
|
|
68
90
|
const columnsToForceQuote = columns.map(col => {
|
|
69
91
|
return col.type !== AdminForthDataTypes.FLOAT
|
|
70
92
|
&& col.type !== AdminForthDataTypes.INTEGER
|
|
@@ -85,10 +107,11 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
85
107
|
server.endpoint({
|
|
86
108
|
method: 'POST',
|
|
87
109
|
path: `/plugin/${this.pluginInstanceId}/import-csv`,
|
|
88
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
110
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, query, headers, cookies, requestUrl, response }) {
|
|
89
111
|
const { data } = body;
|
|
90
112
|
const columns = this.getColumnNames(data);
|
|
91
113
|
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
114
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === this.resourceConfig.resourceId);
|
|
92
115
|
if (errors.length > 0) {
|
|
93
116
|
return { ok: false, errors };
|
|
94
117
|
}
|
|
@@ -97,34 +120,58 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
97
120
|
console.log('Prepared rows for import:', rows);
|
|
98
121
|
let importedCount = 0;
|
|
99
122
|
let updatedCount = 0;
|
|
100
|
-
|
|
123
|
+
const limit = pLimit(100);
|
|
124
|
+
yield Promise.all(rows.map((row) => limit(() => __awaiter(this, void 0, void 0, function* () {
|
|
101
125
|
try {
|
|
102
|
-
|
|
126
|
+
const rowErrors = yield this.isRowValid(row);
|
|
127
|
+
if (rowErrors.length > 0) {
|
|
128
|
+
errors.push(...rowErrors);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const recordId = primaryKeyColumn ? row[primaryKeyColumn.name] : undefined;
|
|
132
|
+
if (primaryKeyColumn && recordId) {
|
|
103
133
|
const existingRecord = yield this.adminforth.resource(this.resourceConfig.resourceId)
|
|
104
|
-
.list([Filters.EQ(primaryKeyColumn.name,
|
|
134
|
+
.list([Filters.EQ(primaryKeyColumn.name, recordId)]);
|
|
105
135
|
if (existingRecord.length > 0) {
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
137
|
+
const oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
138
|
+
if (!oldRecord) {
|
|
139
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
140
|
+
return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
|
|
141
|
+
}
|
|
142
|
+
const { error } = yield this.adminforth.updateResourceRecord({
|
|
143
|
+
resource, updates: row, adminUser, oldRecord, recordId, response,
|
|
144
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
145
|
+
});
|
|
146
|
+
if (error) {
|
|
147
|
+
return { error };
|
|
148
|
+
}
|
|
108
149
|
updatedCount++;
|
|
109
150
|
return;
|
|
110
151
|
}
|
|
111
152
|
}
|
|
112
|
-
yield this.adminforth.
|
|
153
|
+
yield this.adminforth.createResourceRecord({
|
|
154
|
+
resource: resource,
|
|
155
|
+
record: row,
|
|
156
|
+
adminUser: adminUser,
|
|
157
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
158
|
+
});
|
|
113
159
|
importedCount++;
|
|
114
160
|
}
|
|
115
161
|
catch (e) {
|
|
116
162
|
errors.push(e.message);
|
|
117
163
|
}
|
|
118
|
-
})));
|
|
164
|
+
}))));
|
|
119
165
|
return { ok: true, importedCount, updatedCount, errors };
|
|
120
166
|
})
|
|
121
167
|
});
|
|
122
168
|
server.endpoint({
|
|
123
169
|
method: 'POST',
|
|
124
170
|
path: `/plugin/${this.pluginInstanceId}/import-csv-new-only`,
|
|
125
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
171
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, query, headers, cookies, requestUrl, response }) {
|
|
126
172
|
const { data } = body;
|
|
127
173
|
const columns = this.getColumnNames(data);
|
|
174
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === this.resourceConfig.resourceId);
|
|
128
175
|
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
129
176
|
if (errors.length > 0) {
|
|
130
177
|
return { ok: false, errors };
|
|
@@ -132,8 +179,14 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
132
179
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
133
180
|
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
134
181
|
let importedCount = 0;
|
|
135
|
-
|
|
182
|
+
const limit = pLimit(100);
|
|
183
|
+
yield Promise.all(rows.map((row) => limit(() => __awaiter(this, void 0, void 0, function* () {
|
|
136
184
|
try {
|
|
185
|
+
const rowErrors = yield this.isRowValid(row);
|
|
186
|
+
if (rowErrors.length > 0) {
|
|
187
|
+
errors.push(...rowErrors);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
137
190
|
if (primaryKeyColumn && row[primaryKeyColumn.name]) {
|
|
138
191
|
const existingRecord = yield this.adminforth.resource(this.resourceConfig.resourceId)
|
|
139
192
|
.list([Filters.EQ(primaryKeyColumn.name, row[primaryKeyColumn.name])]);
|
|
@@ -141,13 +194,18 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
141
194
|
return;
|
|
142
195
|
}
|
|
143
196
|
}
|
|
144
|
-
yield this.adminforth.
|
|
197
|
+
yield this.adminforth.createResourceRecord({
|
|
198
|
+
resource: resource,
|
|
199
|
+
record: row,
|
|
200
|
+
adminUser: adminUser,
|
|
201
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
202
|
+
});
|
|
145
203
|
importedCount++;
|
|
146
204
|
}
|
|
147
205
|
catch (e) {
|
|
148
206
|
errors.push(e.message);
|
|
149
207
|
}
|
|
150
|
-
})));
|
|
208
|
+
}))));
|
|
151
209
|
return { ok: true, importedCount, errors };
|
|
152
210
|
})
|
|
153
211
|
});
|
package/index.ts
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
|
-
import { AdminForthPlugin, suggestIfTypo, AdminForthFilterOperators, Filters, AdminForthDataTypes } from "adminforth";
|
|
1
|
+
import { AdminForthPlugin, suggestIfTypo, AdminForthFilterOperators, Filters, AdminForthDataTypes, rejectApiRawFilters } from "adminforth";
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthResourceColumn, AdminForthComponentDeclaration, AdminForthResource } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
|
+
import pLimit from 'p-limit';
|
|
4
5
|
|
|
5
6
|
export default class ImportExport extends AdminForthPlugin {
|
|
6
7
|
options: PluginOptions;
|
|
7
8
|
emailField: AdminForthResourceColumn;
|
|
8
9
|
authResourceId: string;
|
|
9
10
|
adminforth: IAdminForth;
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
constructor(options: PluginOptions) {
|
|
12
14
|
super(options, import.meta.url);
|
|
13
15
|
this.options = options;
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
private isRowValid(row: Record<string, unknown>): string[] {
|
|
19
|
+
let errors = [];
|
|
20
|
+
for (const col of Object.keys(row)) {
|
|
21
|
+
const resourceCol = this.resourceConfig.columns.find(c => c.name === col);
|
|
22
|
+
if (!resourceCol) {
|
|
23
|
+
errors.push(`Column '${col}' not found in resource configuration.`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (resourceCol.backendOnly) {
|
|
27
|
+
errors.push(`Column '${col}' is backend only and cannot be imported.`);
|
|
28
|
+
}
|
|
29
|
+
if (resourceCol.enum && !resourceCol.enum.some(e => e.value === row[col])) {
|
|
30
|
+
errors.push(`Column '${col}' has an enum of [${resourceCol.enum.map(e => e.label).join(', ')}] but got value '${row[col]}'.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return errors;
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
17
37
|
super.modifyResourceConfig(adminforth, resourceConfig);
|
|
18
38
|
if (!resourceConfig.options.pageInjections) {
|
|
@@ -55,7 +75,10 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
55
75
|
path: `/plugin/${this.pluginInstanceId}/export-csv`,
|
|
56
76
|
handler: async ({ body }) => {
|
|
57
77
|
const { filters, sort } = body;
|
|
58
|
-
|
|
78
|
+
const rawFilterError = rejectApiRawFilters(body.filters);
|
|
79
|
+
if (rawFilterError) {
|
|
80
|
+
return rawFilterError;
|
|
81
|
+
}
|
|
59
82
|
const data = await this.adminforth.connectors[this.resourceConfig.dataSource].getData({
|
|
60
83
|
resource: this.resourceConfig,
|
|
61
84
|
limit: 1e6,
|
|
@@ -66,7 +89,7 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
66
89
|
});
|
|
67
90
|
|
|
68
91
|
// prepare data for PapaParse unparse
|
|
69
|
-
const columns = this.resourceConfig.columns.filter((col) => !col.virtual);
|
|
92
|
+
const columns = this.resourceConfig.columns.filter((col) => !col.virtual && !col.backendOnly);
|
|
70
93
|
|
|
71
94
|
const columnsToForceQuote = columns.map(col => {
|
|
72
95
|
return col.type !== AdminForthDataTypes.FLOAT
|
|
@@ -92,10 +115,12 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
92
115
|
server.endpoint({
|
|
93
116
|
method: 'POST',
|
|
94
117
|
path: `/plugin/${this.pluginInstanceId}/import-csv`,
|
|
95
|
-
handler: async ({ body }) => {
|
|
118
|
+
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
96
119
|
const { data } = body;
|
|
97
120
|
const columns = this.getColumnNames(data);
|
|
98
121
|
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
122
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === this.resourceConfig.resourceId);
|
|
123
|
+
|
|
99
124
|
if (errors.length > 0) {
|
|
100
125
|
return { ok: false, errors };
|
|
101
126
|
}
|
|
@@ -106,26 +131,49 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
106
131
|
|
|
107
132
|
let importedCount = 0;
|
|
108
133
|
let updatedCount = 0;
|
|
134
|
+
const limit = pLimit(100);
|
|
109
135
|
|
|
110
|
-
await Promise.all(rows.map(async (
|
|
136
|
+
await Promise.all(rows.map((row) => limit(async () => {
|
|
111
137
|
try {
|
|
112
|
-
|
|
138
|
+
const rowErrors = await this.isRowValid(row);
|
|
139
|
+
if (rowErrors.length > 0) {
|
|
140
|
+
errors.push(...rowErrors);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const recordId = primaryKeyColumn ? row[primaryKeyColumn.name] as string : undefined;
|
|
144
|
+
if (primaryKeyColumn && recordId) {
|
|
113
145
|
const existingRecord = await this.adminforth.resource(this.resourceConfig.resourceId)
|
|
114
|
-
.list([Filters.EQ(primaryKeyColumn.name,
|
|
146
|
+
.list([Filters.EQ(primaryKeyColumn.name, recordId)]);
|
|
115
147
|
|
|
116
148
|
if (existingRecord.length > 0) {
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
150
|
+
const oldRecord = await connector.getRecordByPrimaryKey(resource, recordId)
|
|
151
|
+
if (!oldRecord) {
|
|
152
|
+
const primaryKeyColumn = resource.columns.find((col) => col.primaryKey);
|
|
153
|
+
return { error: `Record with ${primaryKeyColumn.name} ${recordId} not found` };
|
|
154
|
+
}
|
|
155
|
+
const { error } = await this.adminforth.updateResourceRecord({
|
|
156
|
+
resource, updates: row, adminUser, oldRecord, recordId, response,
|
|
157
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
158
|
+
});
|
|
159
|
+
if (error) {
|
|
160
|
+
return { error };
|
|
161
|
+
}
|
|
119
162
|
updatedCount++;
|
|
120
163
|
return;
|
|
121
164
|
}
|
|
122
165
|
}
|
|
123
|
-
await this.adminforth.
|
|
166
|
+
await this.adminforth.createResourceRecord({
|
|
167
|
+
resource: resource,
|
|
168
|
+
record: row,
|
|
169
|
+
adminUser: adminUser,
|
|
170
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
171
|
+
});
|
|
124
172
|
importedCount++;
|
|
125
173
|
} catch (e) {
|
|
126
174
|
errors.push(e.message);
|
|
127
175
|
}
|
|
128
|
-
}));
|
|
176
|
+
})));
|
|
129
177
|
|
|
130
178
|
return { ok: true, importedCount, updatedCount, errors };
|
|
131
179
|
}
|
|
@@ -134,9 +182,10 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
134
182
|
server.endpoint({
|
|
135
183
|
method: 'POST',
|
|
136
184
|
path: `/plugin/${this.pluginInstanceId}/import-csv-new-only`,
|
|
137
|
-
handler: async ({ body }) => {
|
|
185
|
+
handler: async ({ body, adminUser, query, headers, cookies, requestUrl, response }) => {
|
|
138
186
|
const { data } = body;
|
|
139
187
|
const columns = this.getColumnNames(data);
|
|
188
|
+
const resource = this.adminforth.config.resources.find(r => r.resourceId === this.resourceConfig.resourceId);
|
|
140
189
|
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
141
190
|
if (errors.length > 0) {
|
|
142
191
|
return { ok: false, errors };
|
|
@@ -146,9 +195,15 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
146
195
|
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
147
196
|
|
|
148
197
|
let importedCount = 0;
|
|
198
|
+
const limit = pLimit(100);
|
|
149
199
|
|
|
150
|
-
await Promise.all(rows.map(async (
|
|
200
|
+
await Promise.all(rows.map((row) => limit(async () => {
|
|
151
201
|
try {
|
|
202
|
+
const rowErrors = await this.isRowValid(row);
|
|
203
|
+
if (rowErrors.length > 0) {
|
|
204
|
+
errors.push(...rowErrors);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
152
207
|
if (primaryKeyColumn && row[primaryKeyColumn.name]) {
|
|
153
208
|
const existingRecord = await this.adminforth.resource(this.resourceConfig.resourceId)
|
|
154
209
|
.list([Filters.EQ(primaryKeyColumn.name, row[primaryKeyColumn.name])]);
|
|
@@ -157,12 +212,17 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
157
212
|
return;
|
|
158
213
|
}
|
|
159
214
|
}
|
|
160
|
-
await this.adminforth.
|
|
215
|
+
await this.adminforth.createResourceRecord({
|
|
216
|
+
resource: resource,
|
|
217
|
+
record: row,
|
|
218
|
+
adminUser: adminUser,
|
|
219
|
+
extra: { body, query, headers, cookies, requestUrl, response }
|
|
220
|
+
});
|
|
161
221
|
importedCount++;
|
|
162
222
|
} catch (e) {
|
|
163
223
|
errors.push(e.message);
|
|
164
224
|
}
|
|
165
|
-
}));
|
|
225
|
+
})));
|
|
166
226
|
|
|
167
227
|
return { ok: true, importedCount, errors };
|
|
168
228
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/import-export",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.26",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.10.7",
|
|
32
|
-
"adminforth": "^3.
|
|
32
|
+
"adminforth": "^3.4.0",
|
|
33
33
|
"semantic-release": "^24.2.1",
|
|
34
34
|
"semantic-release-slack-bot": "^4.0.2",
|
|
35
35
|
"typescript": "^5.7.3"
|
|
@@ -58,5 +58,8 @@
|
|
|
58
58
|
"prerelease": true
|
|
59
59
|
}
|
|
60
60
|
]
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"p-limit": "^7.3.0"
|
|
61
64
|
}
|
|
62
65
|
}
|