Skip to content

Commit 024b271

Browse files
committed
refactor: streamline CSV import handling by consolidating column validation and row building logic
1 parent 38b64c2 commit 024b271

File tree

1 file changed

+90
-82
lines changed

1 file changed

+90
-82
lines changed

index.ts

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -96,47 +96,13 @@ export default class ImportExport extends AdminForthPlugin {
9696
noAuth: true,
9797
handler: async ({ body }) => {
9898
const { data } = body;
99-
const rows = [];
100-
const columns = Object.keys(data);
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);
112101
if (errors.length > 0) {
113102
return { ok: false, errors };
114103
}
115-
116104
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 });
140106

141107
console.log('Prepared rows for import:', rows);
142108

@@ -173,45 +139,14 @@ export default class ImportExport extends AdminForthPlugin {
173139
noAuth: true,
174140
handler: async ({ body }) => {
175141
const { data } = body;
176-
const rows = [];
177-
const columns = Object.keys(data);
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);
189144
if (errors.length > 0) {
190145
return { ok: false, errors };
191146
}
192147

193148
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
194-
const resourceColumns = columns.map(colName => this.resourceConfig.columns.find(c => c.name === colName));
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 });
215150

216151
let importedCount = 0;
217152

@@ -243,19 +178,11 @@ export default class ImportExport extends AdminForthPlugin {
243178
handler: async ({ body }) => {
244179
const { data } = body as { data: Record<string, unknown[]> };
245180
const primaryKeyColumn = this.resourceConfig.columns.find(col => col.primaryKey);
246-
const columns = Object.keys(data);
247-
const columnValues = Object.values(data);
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 });
256183

257184
const primaryKeys = rows
258-
.map(row => row[primaryKeyColumn.name])
185+
.map(row => primaryKeyColumn ? row[primaryKeyColumn.name] : undefined)
259186
.filter(key => key !== undefined && key !== null && key !== '');
260187

261188
const existingRecords = await this.adminforth
@@ -277,4 +204,85 @@ export default class ImportExport extends AdminForthPlugin {
277204
});
278205
}
279206

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+
280288
}

0 commit comments

Comments
 (0)