1- import { AdminForthPlugin , suggestIfTypo , AdminForthFilterOperators , Filters , AdminForthDataTypes } from "adminforth" ;
1+ import { AdminForthPlugin , suggestIfTypo , AdminForthFilterOperators , Filters , AdminForthDataTypes , rejectApiRawFilters } from "adminforth" ;
22import type { IAdminForth , IHttpServer , AdminForthResourceColumn , AdminForthComponentDeclaration , AdminForthResource } from "adminforth" ;
33import type { PluginOptions } from './types.js' ;
4+ import pLimit from 'p-limit' ;
45
56export default class ImportExport extends AdminForthPlugin {
67 options : PluginOptions ;
78 emailField : AdminForthResourceColumn ;
89 authResourceId : string ;
910 adminforth : IAdminForth ;
11+
1012
1113 constructor ( options : PluginOptions ) {
1214 super ( options , import . meta. url ) ;
1315 this . options = options ;
1416 }
1517
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+
1636 async modifyResourceConfig ( adminforth : IAdminForth , resourceConfig : AdminForthResource ) {
1737 super . modifyResourceConfig ( adminforth , resourceConfig ) ;
1838 if ( ! resourceConfig . options . pageInjections ) {
@@ -55,7 +75,10 @@ export default class ImportExport extends AdminForthPlugin {
5575 path : `/plugin/${ this . pluginInstanceId } /export-csv` ,
5676 handler : async ( { body } ) => {
5777 const { filters, sort } = body ;
58-
78+ const rawFilterError = rejectApiRawFilters ( body . filters ) ;
79+ if ( rawFilterError ) {
80+ return rawFilterError ;
81+ }
5982 const data = await this . adminforth . connectors [ this . resourceConfig . dataSource ] . getData ( {
6083 resource : this . resourceConfig ,
6184 limit : 1e6 ,
@@ -66,7 +89,7 @@ export default class ImportExport extends AdminForthPlugin {
6689 } ) ;
6790
6891 // 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 ) ;
7093
7194 const columnsToForceQuote = columns . map ( col => {
7295 return col . type !== AdminForthDataTypes . FLOAT
@@ -92,10 +115,12 @@ export default class ImportExport extends AdminForthPlugin {
92115 server . endpoint ( {
93116 method : 'POST' ,
94117 path : `/plugin/${ this . pluginInstanceId } /import-csv` ,
95- handler : async ( { body } ) => {
118+ handler : async ( { body, adminUser , query , headers , cookies , requestUrl , response } ) => {
96119 const { data } = body ;
97120 const columns = this . getColumnNames ( data ) ;
98121 const { errors, resourceColumns } = this . validateColumns ( columns ) ;
122+ const resource = this . adminforth . config . resources . find ( r => r . resourceId === this . resourceConfig . resourceId ) ;
123+
99124 if ( errors . length > 0 ) {
100125 return { ok : false , errors } ;
101126 }
@@ -106,26 +131,49 @@ export default class ImportExport extends AdminForthPlugin {
106131
107132 let importedCount = 0 ;
108133 let updatedCount = 0 ;
134+ const limit = pLimit ( 100 ) ;
109135
110- await Promise . all ( rows . map ( async ( row ) => {
136+ await Promise . all ( rows . map ( ( row ) => limit ( async ( ) => {
111137 try {
112- if ( primaryKeyColumn && row [ primaryKeyColumn . name ] ) {
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 ) {
113145 const existingRecord = await this . adminforth . resource ( this . resourceConfig . resourceId )
114- . list ( [ Filters . EQ ( primaryKeyColumn . name , row [ primaryKeyColumn . name ] ) ] ) ;
146+ . list ( [ Filters . EQ ( primaryKeyColumn . name , recordId ) ] ) ;
115147
116148 if ( existingRecord . length > 0 ) {
117- await this . adminforth . resource ( this . resourceConfig . resourceId )
118- . update ( row [ primaryKeyColumn . name ] , row ) ;
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+ }
119162 updatedCount ++ ;
120163 return ;
121164 }
122165 }
123- await this . adminforth . resource ( this . resourceConfig . resourceId ) . create ( row ) ;
166+ await this . adminforth . createResourceRecord ( {
167+ resource : resource ,
168+ record : row ,
169+ adminUser : adminUser ,
170+ extra : { body, query, headers, cookies, requestUrl, response }
171+ } ) ;
124172 importedCount ++ ;
125173 } catch ( e ) {
126174 errors . push ( e . message ) ;
127175 }
128- } ) ) ;
176+ } ) ) ) ;
129177
130178 return { ok : true , importedCount, updatedCount, errors } ;
131179 }
@@ -134,9 +182,10 @@ export default class ImportExport extends AdminForthPlugin {
134182 server . endpoint ( {
135183 method : 'POST' ,
136184 path : `/plugin/${ this . pluginInstanceId } /import-csv-new-only` ,
137- handler : async ( { body } ) => {
185+ handler : async ( { body, adminUser , query , headers , cookies , requestUrl , response } ) => {
138186 const { data } = body ;
139187 const columns = this . getColumnNames ( data ) ;
188+ const resource = this . adminforth . config . resources . find ( r => r . resourceId === this . resourceConfig . resourceId ) ;
140189 const { errors, resourceColumns } = this . validateColumns ( columns ) ;
141190 if ( errors . length > 0 ) {
142191 return { ok : false , errors } ;
@@ -146,9 +195,15 @@ export default class ImportExport extends AdminForthPlugin {
146195 const rows = this . buildRowsFromData ( data , columns , resourceColumns , { coerceTypes : true } ) ;
147196
148197 let importedCount = 0 ;
198+ const limit = pLimit ( 100 ) ;
149199
150- await Promise . all ( rows . map ( async ( row ) => {
200+ await Promise . all ( rows . map ( ( row ) => limit ( async ( ) => {
151201 try {
202+ const rowErrors = await this . isRowValid ( row ) ;
203+ if ( rowErrors . length > 0 ) {
204+ errors . push ( ...rowErrors ) ;
205+ return ;
206+ }
152207 if ( primaryKeyColumn && row [ primaryKeyColumn . name ] ) {
153208 const existingRecord = await this . adminforth . resource ( this . resourceConfig . resourceId )
154209 . list ( [ Filters . EQ ( primaryKeyColumn . name , row [ primaryKeyColumn . name ] ) ] ) ;
@@ -157,12 +212,17 @@ export default class ImportExport extends AdminForthPlugin {
157212 return ;
158213 }
159214 }
160- await this . adminforth . resource ( this . resourceConfig . resourceId ) . create ( row ) ;
215+ await this . adminforth . createResourceRecord ( {
216+ resource : resource ,
217+ record : row ,
218+ adminUser : adminUser ,
219+ extra : { body, query, headers, cookies, requestUrl, response }
220+ } ) ;
161221 importedCount ++ ;
162222 } catch ( e ) {
163223 errors . push ( e . message ) ;
164224 }
165- } ) ) ;
225+ } ) ) ) ;
166226
167227 return { ok : true , importedCount, errors } ;
168228 }
0 commit comments