@adminforth/import-export 1.4.9 → 1.4.11
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/.woodpecker/release.yml +4 -4
- package/build.log +3 -2
- package/custom/package-lock.json +13 -13
- package/custom/pnpm-lock.yaml +44 -0
- package/dist/custom/package-lock.json +13 -13
- package/dist/custom/pnpm-lock.yaml +44 -0
- package/dist/index.js +64 -72
- package/index.ts +90 -82
- package/package.json +4 -4
package/.woodpecker/release.yml
CHANGED
|
@@ -17,18 +17,18 @@ steps:
|
|
|
17
17
|
- infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
|
|
18
18
|
|
|
19
19
|
build:
|
|
20
|
-
image:
|
|
20
|
+
image: devforth/node20-pnpm:latest
|
|
21
21
|
when:
|
|
22
22
|
- event: push
|
|
23
23
|
commands:
|
|
24
24
|
- apt update && apt install -y rsync
|
|
25
25
|
- . /woodpecker/deploy.vault.env
|
|
26
|
-
-
|
|
26
|
+
- pnpm install
|
|
27
27
|
- /bin/bash ./.woodpecker/buildRelease.sh
|
|
28
28
|
- npm audit signatures
|
|
29
29
|
|
|
30
30
|
release:
|
|
31
|
-
image:
|
|
31
|
+
image: devforth/node20-pnpm:latest
|
|
32
32
|
when:
|
|
33
33
|
- event:
|
|
34
34
|
- push
|
|
@@ -36,7 +36,7 @@ steps:
|
|
|
36
36
|
- main
|
|
37
37
|
commands:
|
|
38
38
|
- . /woodpecker/deploy.vault.env
|
|
39
|
-
-
|
|
39
|
+
- pnpm exec semantic-release
|
|
40
40
|
|
|
41
41
|
slack-on-failure:
|
|
42
42
|
image: curlimages/curl
|
package/build.log
CHANGED
|
@@ -8,7 +8,8 @@ custom/ExportCsv.vue
|
|
|
8
8
|
custom/ImportCsv.vue
|
|
9
9
|
custom/package-lock.json
|
|
10
10
|
custom/package.json
|
|
11
|
+
custom/pnpm-lock.yaml
|
|
11
12
|
custom/tsconfig.json
|
|
12
13
|
|
|
13
|
-
sent
|
|
14
|
-
total size is
|
|
14
|
+
sent 17,847 bytes received 134 bytes 35,962.00 bytes/sec
|
|
15
|
+
total size is 17,365 speedup is 0.97
|
package/custom/package-lock.json
CHANGED
|
@@ -14,33 +14,33 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"node_modules/@types/node": {
|
|
17
|
-
"version": "
|
|
18
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-
|
|
19
|
-
"integrity": "sha512-
|
|
17
|
+
"version": "25.3.5",
|
|
18
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
|
|
19
|
+
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"undici-types": "~
|
|
22
|
+
"undici-types": "~7.18.0"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"node_modules/@types/papaparse": {
|
|
26
|
-
"version": "5.
|
|
27
|
-
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.
|
|
28
|
-
"integrity": "sha512-
|
|
26
|
+
"version": "5.5.2",
|
|
27
|
+
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
|
|
28
|
+
"integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@types/node": "*"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"node_modules/papaparse": {
|
|
35
|
-
"version": "5.5.
|
|
36
|
-
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.
|
|
37
|
-
"integrity": "sha512-
|
|
35
|
+
"version": "5.5.3",
|
|
36
|
+
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
|
|
37
|
+
"integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
|
|
38
38
|
"license": "MIT"
|
|
39
39
|
},
|
|
40
40
|
"node_modules/undici-types": {
|
|
41
|
-
"version": "
|
|
42
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-
|
|
43
|
-
"integrity": "sha512-
|
|
41
|
+
"version": "7.18.2",
|
|
42
|
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
|
43
|
+
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
|
44
44
|
"license": "MIT"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
lockfileVersion: '9.0'
|
|
2
|
+
|
|
3
|
+
settings:
|
|
4
|
+
autoInstallPeers: true
|
|
5
|
+
excludeLinksFromLockfile: false
|
|
6
|
+
|
|
7
|
+
importers:
|
|
8
|
+
|
|
9
|
+
.:
|
|
10
|
+
dependencies:
|
|
11
|
+
'@types/papaparse':
|
|
12
|
+
specifier: ^5.3.15
|
|
13
|
+
version: 5.5.2
|
|
14
|
+
papaparse:
|
|
15
|
+
specifier: ^5.5.2
|
|
16
|
+
version: 5.5.3
|
|
17
|
+
|
|
18
|
+
packages:
|
|
19
|
+
|
|
20
|
+
'@types/node@25.3.5':
|
|
21
|
+
resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==}
|
|
22
|
+
|
|
23
|
+
'@types/papaparse@5.5.2':
|
|
24
|
+
resolution: {integrity: sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==}
|
|
25
|
+
|
|
26
|
+
papaparse@5.5.3:
|
|
27
|
+
resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==}
|
|
28
|
+
|
|
29
|
+
undici-types@7.18.2:
|
|
30
|
+
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
|
31
|
+
|
|
32
|
+
snapshots:
|
|
33
|
+
|
|
34
|
+
'@types/node@25.3.5':
|
|
35
|
+
dependencies:
|
|
36
|
+
undici-types: 7.18.2
|
|
37
|
+
|
|
38
|
+
'@types/papaparse@5.5.2':
|
|
39
|
+
dependencies:
|
|
40
|
+
'@types/node': 25.3.5
|
|
41
|
+
|
|
42
|
+
papaparse@5.5.3: {}
|
|
43
|
+
|
|
44
|
+
undici-types@7.18.2: {}
|
|
@@ -14,33 +14,33 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"node_modules/@types/node": {
|
|
17
|
-
"version": "
|
|
18
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-
|
|
19
|
-
"integrity": "sha512-
|
|
17
|
+
"version": "25.3.5",
|
|
18
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
|
|
19
|
+
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"undici-types": "~
|
|
22
|
+
"undici-types": "~7.18.0"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"node_modules/@types/papaparse": {
|
|
26
|
-
"version": "5.
|
|
27
|
-
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.
|
|
28
|
-
"integrity": "sha512-
|
|
26
|
+
"version": "5.5.2",
|
|
27
|
+
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
|
|
28
|
+
"integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@types/node": "*"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"node_modules/papaparse": {
|
|
35
|
-
"version": "5.5.
|
|
36
|
-
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.
|
|
37
|
-
"integrity": "sha512-
|
|
35
|
+
"version": "5.5.3",
|
|
36
|
+
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
|
|
37
|
+
"integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
|
|
38
38
|
"license": "MIT"
|
|
39
39
|
},
|
|
40
40
|
"node_modules/undici-types": {
|
|
41
|
-
"version": "
|
|
42
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-
|
|
43
|
-
"integrity": "sha512-
|
|
41
|
+
"version": "7.18.2",
|
|
42
|
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
|
43
|
+
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
|
44
44
|
"license": "MIT"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
lockfileVersion: '9.0'
|
|
2
|
+
|
|
3
|
+
settings:
|
|
4
|
+
autoInstallPeers: true
|
|
5
|
+
excludeLinksFromLockfile: false
|
|
6
|
+
|
|
7
|
+
importers:
|
|
8
|
+
|
|
9
|
+
.:
|
|
10
|
+
dependencies:
|
|
11
|
+
'@types/papaparse':
|
|
12
|
+
specifier: ^5.3.15
|
|
13
|
+
version: 5.5.2
|
|
14
|
+
papaparse:
|
|
15
|
+
specifier: ^5.5.2
|
|
16
|
+
version: 5.5.3
|
|
17
|
+
|
|
18
|
+
packages:
|
|
19
|
+
|
|
20
|
+
'@types/node@25.3.5':
|
|
21
|
+
resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==}
|
|
22
|
+
|
|
23
|
+
'@types/papaparse@5.5.2':
|
|
24
|
+
resolution: {integrity: sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==}
|
|
25
|
+
|
|
26
|
+
papaparse@5.5.3:
|
|
27
|
+
resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==}
|
|
28
|
+
|
|
29
|
+
undici-types@7.18.2:
|
|
30
|
+
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
|
31
|
+
|
|
32
|
+
snapshots:
|
|
33
|
+
|
|
34
|
+
'@types/node@25.3.5':
|
|
35
|
+
dependencies:
|
|
36
|
+
undici-types: 7.18.2
|
|
37
|
+
|
|
38
|
+
'@types/papaparse@5.5.2':
|
|
39
|
+
dependencies:
|
|
40
|
+
'@types/node': 25.3.5
|
|
41
|
+
|
|
42
|
+
papaparse@5.5.3: {}
|
|
43
|
+
|
|
44
|
+
undici-types@7.18.2: {}
|
package/dist/index.js
CHANGED
|
@@ -89,41 +89,13 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
89
89
|
noAuth: true,
|
|
90
90
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
91
91
|
const { data } = body;
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
// check column names are valid
|
|
95
|
-
const errors = [];
|
|
96
|
-
columns.forEach((col) => {
|
|
97
|
-
if (!this.resourceConfig.columns.some((c) => c.name === col)) {
|
|
98
|
-
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
99
|
-
errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
92
|
+
const columns = this.getColumnNames(data);
|
|
93
|
+
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
102
94
|
if (errors.length > 0) {
|
|
103
95
|
return { ok: false, errors };
|
|
104
96
|
}
|
|
105
97
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
106
|
-
const
|
|
107
|
-
const columnValues = Object.values(data);
|
|
108
|
-
for (let i = 0; i < columnValues[0].length; i++) {
|
|
109
|
-
const row = {};
|
|
110
|
-
for (let j = 0; j < columns.length; j++) {
|
|
111
|
-
const val = columnValues[j][i];
|
|
112
|
-
const resourceCol = resourceColumns[j];
|
|
113
|
-
if ((resourceCol.type === AdminForthDataTypes.INTEGER
|
|
114
|
-
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== '') {
|
|
115
|
-
// convert empty strings to null for numeric fields
|
|
116
|
-
row[columns[j]] = +val;
|
|
117
|
-
}
|
|
118
|
-
else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
|
|
119
|
-
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
row[columns[j]] = val;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
rows.push(row);
|
|
126
|
-
}
|
|
98
|
+
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
127
99
|
console.log('Prepared rows for import:', rows);
|
|
128
100
|
let importedCount = 0;
|
|
129
101
|
let updatedCount = 0;
|
|
@@ -155,41 +127,13 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
155
127
|
noAuth: true,
|
|
156
128
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
157
129
|
const { data } = body;
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
// check column names are valid
|
|
161
|
-
const errors = [];
|
|
162
|
-
columns.forEach((col) => {
|
|
163
|
-
if (!this.resourceConfig.columns.some((c) => c.name === col)) {
|
|
164
|
-
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
165
|
-
errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
130
|
+
const columns = this.getColumnNames(data);
|
|
131
|
+
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
168
132
|
if (errors.length > 0) {
|
|
169
133
|
return { ok: false, errors };
|
|
170
134
|
}
|
|
171
135
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
172
|
-
const
|
|
173
|
-
const columnValues = Object.values(data);
|
|
174
|
-
for (let i = 0; i < columnValues[0].length; i++) {
|
|
175
|
-
const row = {};
|
|
176
|
-
for (let j = 0; j < columns.length; j++) {
|
|
177
|
-
const val = columnValues[j][i];
|
|
178
|
-
const resourceCol = resourceColumns[j];
|
|
179
|
-
if ((resourceCol.type === AdminForthDataTypes.INTEGER
|
|
180
|
-
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== '') {
|
|
181
|
-
// convert empty strings to null for numeric fields
|
|
182
|
-
row[columns[j]] = +val;
|
|
183
|
-
}
|
|
184
|
-
else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
|
|
185
|
-
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
row[columns[j]] = val;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
rows.push(row);
|
|
192
|
-
}
|
|
136
|
+
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
193
137
|
let importedCount = 0;
|
|
194
138
|
yield Promise.all(rows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
195
139
|
try {
|
|
@@ -217,17 +161,10 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
217
161
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
218
162
|
const { data } = body;
|
|
219
163
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
220
|
-
const columns =
|
|
221
|
-
const
|
|
222
|
-
const rows = Array.from({ length: columnValues[0].length }, (_, i) => {
|
|
223
|
-
const row = {};
|
|
224
|
-
for (let j = 0; j < columns.length; j++) {
|
|
225
|
-
row[columns[j]] = columnValues[j][i];
|
|
226
|
-
}
|
|
227
|
-
return row;
|
|
228
|
-
});
|
|
164
|
+
const columns = this.getColumnNames(data);
|
|
165
|
+
const rows = this.buildRowsFromData(data, columns, undefined, { coerceTypes: false });
|
|
229
166
|
const primaryKeys = rows
|
|
230
|
-
.map(row => row[primaryKeyColumn.name])
|
|
167
|
+
.map(row => primaryKeyColumn ? row[primaryKeyColumn.name] : undefined)
|
|
231
168
|
.filter(key => key !== undefined && key !== null && key !== '');
|
|
232
169
|
const existingRecords = yield this.adminforth
|
|
233
170
|
.resource(this.resourceConfig.resourceId)
|
|
@@ -245,4 +182,59 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
245
182
|
})
|
|
246
183
|
});
|
|
247
184
|
}
|
|
185
|
+
getColumnNames(data) {
|
|
186
|
+
return Object.keys(data !== null && data !== void 0 ? data : {});
|
|
187
|
+
}
|
|
188
|
+
validateColumns(columns) {
|
|
189
|
+
const errors = [];
|
|
190
|
+
const resourceColumns = [];
|
|
191
|
+
columns.forEach((col) => {
|
|
192
|
+
const resourceColumn = this.resourceConfig.columns.find((c) => c.name === col);
|
|
193
|
+
if (!resourceColumn) {
|
|
194
|
+
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
195
|
+
errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${similar
|
|
196
|
+
? `If you mean '${similar}', rename it in CSV`
|
|
197
|
+
: 'If column is in database but not in resource configuration, add it with showIn:[]'}`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
resourceColumns.push(resourceColumn);
|
|
201
|
+
});
|
|
202
|
+
return { errors, resourceColumns };
|
|
203
|
+
}
|
|
204
|
+
buildRowsFromData(data, columns, resourceColumns, { coerceTypes } = { coerceTypes: true }) {
|
|
205
|
+
const columnValues = Object.values(data !== null && data !== void 0 ? data : {});
|
|
206
|
+
if (columns.length === 0 || columnValues.length === 0) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
const rows = [];
|
|
210
|
+
const rowCount = columnValues[0].length;
|
|
211
|
+
for (let i = 0; i < rowCount; i++) {
|
|
212
|
+
const row = {};
|
|
213
|
+
for (let j = 0; j < columns.length; j++) {
|
|
214
|
+
const val = columnValues[j][i];
|
|
215
|
+
const resourceCol = resourceColumns ? resourceColumns[j] : undefined;
|
|
216
|
+
row[columns[j]] = coerceTypes
|
|
217
|
+
? this.coerceValue(resourceCol, val)
|
|
218
|
+
: val;
|
|
219
|
+
}
|
|
220
|
+
rows.push(row);
|
|
221
|
+
}
|
|
222
|
+
return rows;
|
|
223
|
+
}
|
|
224
|
+
coerceValue(resourceCol, val) {
|
|
225
|
+
if (!resourceCol || val === '') {
|
|
226
|
+
return val;
|
|
227
|
+
}
|
|
228
|
+
if ((resourceCol.type === AdminForthDataTypes.INTEGER
|
|
229
|
+
|| resourceCol.type === AdminForthDataTypes.FLOAT)) {
|
|
230
|
+
return +val;
|
|
231
|
+
}
|
|
232
|
+
if (resourceCol.type === AdminForthDataTypes.BOOLEAN) {
|
|
233
|
+
if (typeof val === 'string') {
|
|
234
|
+
return val.toLowerCase() === 'true' || val === '1';
|
|
235
|
+
}
|
|
236
|
+
return val === 1 || val === true;
|
|
237
|
+
}
|
|
238
|
+
return val;
|
|
239
|
+
}
|
|
248
240
|
}
|
package/index.ts
CHANGED
|
@@ -96,47 +96,13 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
96
96
|
noAuth: true,
|
|
97
97
|
handler: async ({ body }) => {
|
|
98
98
|
const { data } = body;
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
// check column names are valid
|
|
103
|
-
const errors: string[] = [];
|
|
104
|
-
columns.forEach((col) => {
|
|
105
|
-
if (!this.resourceConfig.columns.some((c) => c.name === col)) {
|
|
106
|
-
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
107
|
-
errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
|
|
108
|
-
similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
99
|
+
const columns = this.getColumnNames(data);
|
|
100
|
+
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
112
101
|
if (errors.length > 0) {
|
|
113
102
|
return { ok: false, errors };
|
|
114
103
|
}
|
|
115
|
-
|
|
116
104
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
117
|
-
|
|
118
|
-
const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
|
|
119
|
-
|
|
120
|
-
const columnValues: any[] = Object.values(data);
|
|
121
|
-
for (let i = 0; i < columnValues[0].length; i++) {
|
|
122
|
-
const row = {};
|
|
123
|
-
for (let j = 0; j < columns.length; j++) {
|
|
124
|
-
const val = columnValues[j][i];
|
|
125
|
-
const resourceCol = resourceColumns[j];
|
|
126
|
-
|
|
127
|
-
if ( (resourceCol.type === AdminForthDataTypes.INTEGER
|
|
128
|
-
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
|
|
129
|
-
) {
|
|
130
|
-
// convert empty strings to null for numeric fields
|
|
131
|
-
row[columns[j]] = +val;
|
|
132
|
-
} else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
|
|
133
|
-
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
|
|
134
|
-
} else {
|
|
135
|
-
row[columns[j]] = val;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
rows.push(row);
|
|
139
|
-
}
|
|
105
|
+
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
140
106
|
|
|
141
107
|
console.log('Prepared rows for import:', rows);
|
|
142
108
|
|
|
@@ -173,45 +139,14 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
173
139
|
noAuth: true,
|
|
174
140
|
handler: async ({ body }) => {
|
|
175
141
|
const { data } = body;
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
// check column names are valid
|
|
180
|
-
const errors: string[] = [];
|
|
181
|
-
columns.forEach((col) => {
|
|
182
|
-
if (!this.resourceConfig.columns.some((c) => c.name === col)) {
|
|
183
|
-
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
184
|
-
errors.push(`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
|
|
185
|
-
similar ? `If you mean '${similar}', rename it in CSV` : 'If column is in database but not in resource configuration, add it with showIn:[]'}`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
142
|
+
const columns = this.getColumnNames(data);
|
|
143
|
+
const { errors, resourceColumns } = this.validateColumns(columns);
|
|
189
144
|
if (errors.length > 0) {
|
|
190
145
|
return { ok: false, errors };
|
|
191
146
|
}
|
|
192
147
|
|
|
193
148
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
194
|
-
const
|
|
195
|
-
const columnValues: any[] = Object.values(data);
|
|
196
|
-
for (let i = 0; i < columnValues[0].length; i++) {
|
|
197
|
-
const row = {};
|
|
198
|
-
for (let j = 0; j < columns.length; j++) {
|
|
199
|
-
const val = columnValues[j][i];
|
|
200
|
-
const resourceCol = resourceColumns[j];
|
|
201
|
-
|
|
202
|
-
if ( (resourceCol.type === AdminForthDataTypes.INTEGER
|
|
203
|
-
|| resourceCol.type === AdminForthDataTypes.FLOAT) && val !== ''
|
|
204
|
-
) {
|
|
205
|
-
// convert empty strings to null for numeric fields
|
|
206
|
-
row[columns[j]] = +val;
|
|
207
|
-
} else if (resourceCol.type === AdminForthDataTypes.BOOLEAN && val !== '') {
|
|
208
|
-
row[columns[j]] = (val.toLowerCase() === 'true' || val === '1' || val === 1);
|
|
209
|
-
} else {
|
|
210
|
-
row[columns[j]] = val;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
rows.push(row);
|
|
214
|
-
}
|
|
149
|
+
const rows = this.buildRowsFromData(data, columns, resourceColumns, { coerceTypes: true });
|
|
215
150
|
|
|
216
151
|
let importedCount = 0;
|
|
217
152
|
|
|
@@ -243,19 +178,11 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
243
178
|
handler: async ({ body }) => {
|
|
244
179
|
const { data } = body as { data: Record<string, unknown[]> };
|
|
245
180
|
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
|
|
246
|
-
const columns =
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
const rows = Array.from({ length: columnValues[0].length }, (_, i) => {
|
|
250
|
-
const row = {};
|
|
251
|
-
for (let j = 0; j < columns.length; j++) {
|
|
252
|
-
row[columns[j]] = columnValues[j][i];
|
|
253
|
-
}
|
|
254
|
-
return row;
|
|
255
|
-
});
|
|
181
|
+
const columns = this.getColumnNames(data);
|
|
182
|
+
const rows = this.buildRowsFromData(data, columns, undefined, { coerceTypes: false });
|
|
256
183
|
|
|
257
184
|
const primaryKeys = rows
|
|
258
|
-
.map(row => row[primaryKeyColumn.name])
|
|
185
|
+
.map(row => primaryKeyColumn ? row[primaryKeyColumn.name] : undefined)
|
|
259
186
|
.filter(key => key !== undefined && key !== null && key !== '');
|
|
260
187
|
|
|
261
188
|
const existingRecords = await this.adminforth
|
|
@@ -277,4 +204,85 @@ export default class ImportExport extends AdminForthPlugin {
|
|
|
277
204
|
});
|
|
278
205
|
}
|
|
279
206
|
|
|
207
|
+
private getColumnNames(data: Record<string, unknown[]>): string[] {
|
|
208
|
+
return Object.keys(data ?? {});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private validateColumns(columns: string[]): {
|
|
212
|
+
errors: string[];
|
|
213
|
+
resourceColumns: AdminForthResourceColumn[];
|
|
214
|
+
} {
|
|
215
|
+
const errors: string[] = [];
|
|
216
|
+
const resourceColumns: AdminForthResourceColumn[] = [];
|
|
217
|
+
|
|
218
|
+
columns.forEach((col) => {
|
|
219
|
+
const resourceColumn = this.resourceConfig.columns.find((c) => c.name === col);
|
|
220
|
+
if (!resourceColumn) {
|
|
221
|
+
const similar = suggestIfTypo(this.resourceConfig.columns.map((c) => c.name), col);
|
|
222
|
+
errors.push(
|
|
223
|
+
`Column '${col}' defined in CSV not found in resource '${this.resourceConfig.resourceId}'. ${
|
|
224
|
+
similar
|
|
225
|
+
? `If you mean '${similar}', rename it in CSV`
|
|
226
|
+
: 'If column is in database but not in resource configuration, add it with showIn:[]'
|
|
227
|
+
}`
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
resourceColumns.push(resourceColumn);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return { errors, resourceColumns };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private buildRowsFromData(
|
|
238
|
+
data: Record<string, unknown[]>,
|
|
239
|
+
columns: string[],
|
|
240
|
+
resourceColumns?: AdminForthResourceColumn[],
|
|
241
|
+
{ coerceTypes }: { coerceTypes: boolean } = { coerceTypes: true }
|
|
242
|
+
) {
|
|
243
|
+
const columnValues: unknown[][] = Object.values(data ?? {});
|
|
244
|
+
if (columns.length === 0 || columnValues.length === 0) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const rows: Record<string, unknown>[] = [];
|
|
249
|
+
const rowCount = columnValues[0].length;
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < rowCount; i++) {
|
|
252
|
+
const row: Record<string, unknown> = {};
|
|
253
|
+
for (let j = 0; j < columns.length; j++) {
|
|
254
|
+
const val = columnValues[j][i];
|
|
255
|
+
const resourceCol = resourceColumns ? resourceColumns[j] : undefined;
|
|
256
|
+
row[columns[j]] = coerceTypes
|
|
257
|
+
? this.coerceValue(resourceCol, val)
|
|
258
|
+
: val;
|
|
259
|
+
}
|
|
260
|
+
rows.push(row);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return rows;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private coerceValue(resourceCol: AdminForthResourceColumn | undefined, val: unknown): unknown {
|
|
267
|
+
if (!resourceCol || val === '') {
|
|
268
|
+
return val;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
(resourceCol.type === AdminForthDataTypes.INTEGER
|
|
273
|
+
|| resourceCol.type === AdminForthDataTypes.FLOAT)
|
|
274
|
+
) {
|
|
275
|
+
return +val;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (resourceCol.type === AdminForthDataTypes.BOOLEAN) {
|
|
279
|
+
if (typeof val === 'string') {
|
|
280
|
+
return val.toLowerCase() === 'true' || val === '1';
|
|
281
|
+
}
|
|
282
|
+
return val === 1 || val === true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return val;
|
|
286
|
+
}
|
|
287
|
+
|
|
280
288
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/import-export",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.11",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://adminforth.dev/docs/tutorial/Plugins/import-export/",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
13
|
-
"prepare": "npm link adminforth"
|
|
12
|
+
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
14
13
|
},
|
|
15
14
|
"repository": {
|
|
16
15
|
"type": "git",
|
|
@@ -26,7 +25,7 @@
|
|
|
26
25
|
"license": "ISC",
|
|
27
26
|
"description": "CSV import/export plugin for adminforth",
|
|
28
27
|
"peerDependencies": {
|
|
29
|
-
"adminforth": "
|
|
28
|
+
"adminforth": "^2.24.0"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"@types/node": "^22.10.7",
|
|
@@ -43,6 +42,7 @@
|
|
|
43
42
|
[
|
|
44
43
|
"semantic-release-slack-bot",
|
|
45
44
|
{
|
|
45
|
+
"packageName": "@adminforth/import-export",
|
|
46
46
|
"notifyOnSuccess": true,
|
|
47
47
|
"notifyOnFail": true,
|
|
48
48
|
"slackIcon": ":package:",
|