@@ -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