1- import { AdminForthFilterOperators , AdminForthPlugin , Filters } from "adminforth" ;
1+ import { AdminForthFilterOperators , AdminForthPlugin , parseBody , Filters } from "adminforth" ;
22import type { IAdminForth , IHttpServer , AdminForthComponentDeclaration , AdminForthResource } from "adminforth" ;
33import { suggestIfTypo , filtersTools } from "adminforth" ;
44import type { PluginOptions } from './types.js' ;
55import Handlebars from 'handlebars' ;
66import { RateLimiter } from "adminforth" ;
77import { randomUUID } from "crypto" ;
8+ import { z } from "zod" ;
9+
10+ const getRecordsBodySchema = z . object ( {
11+ record : z . array ( z . any ( ) ) . nullish ( ) ,
12+ } ) . strict ( ) ;
13+
14+ const getImagesBodySchema = z . object ( {
15+ record : z . array ( z . any ( ) ) . nullish ( ) ,
16+ } ) . strict ( ) ;
17+
18+ const getOldDataBodySchema = z . object ( {
19+ recordId : z . union ( [ z . string ( ) , z . number ( ) ] ) . nullish ( ) ,
20+ } ) . strict ( ) ;
21+
22+ const updateFieldsBodySchema = z . object ( {
23+ selectedIds : z . array ( z . any ( ) ) . nullish ( ) ,
24+ fields : z . any ( ) ,
25+ saveImages : z . boolean ( ) . nullish ( ) ,
26+ } ) . strict ( ) ;
27+
28+ const getImageGenerationPromptsBodySchema = z . object ( {
29+ recordId : z . union ( [ z . string ( ) , z . number ( ) ] ) . nullish ( ) ,
30+ customPrompt : z . string ( ) . nullish ( ) ,
31+ } ) . strict ( ) ;
32+
33+ const createJobBodySchema = z . object ( {
34+ actionType : z . string ( ) . nullish ( ) ,
35+ recordId : z . any ( ) . optional ( ) ,
36+ customPrompt : z . string ( ) . nullish ( ) ,
37+ filterFilledFields : z . boolean ( ) . nullish ( ) ,
38+ sessionIds : z . array ( z . string ( ) ) . nullish ( ) ,
39+ prompt : z . string ( ) . nullish ( ) ,
40+ fieldName : z . string ( ) . nullish ( ) ,
41+ fieldToRegenerate : z . string ( ) . nullish ( ) ,
42+ action : z . string ( ) . nullish ( ) ,
43+ } ) . strict ( ) ;
44+
45+ const jobStatusBodySchema = z . object ( {
46+ jobId : z . string ( ) ,
47+ } ) . strict ( ) ;
48+
49+ const updateRateLimitsBodySchema = z . object ( {
50+ actionType : z . string ( ) . nullish ( ) ,
51+ } ) . strict ( ) ;
52+
53+ const compileOldImageLinkBodySchema = z . object ( {
54+ image : z . string ( ) . nullish ( ) ,
55+ columnName : z . string ( ) . nullish ( ) ,
56+ } ) . strict ( ) ;
57+
58+ const getFilteredIdsBodySchema = z . object ( {
59+ filters : z . any ( ) ,
60+ } ) . passthrough ( ) ;
861const STUB_MODE = false ;
962const jobs = new Map ( ) ;
1063export default class BulkAiFlowPlugin extends AdminForthPlugin {
@@ -779,17 +832,20 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
779832 server . endpoint ( {
780833 method : 'POST' ,
781834 path : `/plugin/${ this . pluginInstanceId } /get_records` ,
782- handler : async ( body ) => {
783- if ( ! Array . isArray ( body . body ?. record ) ) {
835+ handler : async ( { body, response } ) => {
836+ const parsed = parseBody ( getRecordsBodySchema , body , response ) ;
837+ if ( 'error' in parsed ) return parsed . error ;
838+ const data = parsed . data ;
839+ if ( ! Array . isArray ( data . record ) ) {
784840 return { records : [ ] } ;
785841 }
786842 let records = [ ] ;
787843 const primaryKeyColumn = this . resourceConfig . columns . find ( ( col ) => col . primaryKey ) ;
788- records = await this . adminforth . resource ( this . resourceConfig . resourceId ) . list ( [ Filters . IN ( primaryKeyColumn . name , body . body . record ) ] ) ;
844+ records = await this . adminforth . resource ( this . resourceConfig . resourceId ) . list ( [ Filters . IN ( primaryKeyColumn . name , data . record ) ] ) ;
789845 for ( const [ index , record ] of records . entries ( ) ) {
790846 records [ index ] . _label = this . resourceConfig . recordLabel ( records [ index ] ) ;
791847 }
792- const order = Object . fromEntries ( body . body . record . map ( ( id , i ) => [ id , i ] ) ) ;
848+ const order = Object . fromEntries ( data . record . map ( ( id , i ) => [ id , i ] ) ) ;
793849
794850 const sortedRecords = records . sort (
795851 ( a , b ) => order [ a . id ] - order [ b . id ]
@@ -804,8 +860,11 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
804860 server . endpoint ( {
805861 method : 'POST' ,
806862 path : `/plugin/${ this . pluginInstanceId } /get_old_data` ,
807- handler : async ( { body } ) => {
808- const recordId = body . recordId ;
863+ handler : async ( { body, response } ) => {
864+ const parsed = parseBody ( getOldDataBodySchema , body , response ) ;
865+ if ( 'error' in parsed ) return parsed . error ;
866+ const data = parsed . data ;
867+ const recordId = data . recordId ;
809868 if ( recordId === undefined || recordId === null ) {
810869 return { ok : false , error : "Missing recordId" } ;
811870 }
@@ -824,10 +883,13 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
824883 server . endpoint ( {
825884 method : 'POST' ,
826885 path : `/plugin/${ this . pluginInstanceId } /get_images` ,
827- handler : async ( body ) => {
886+ handler : async ( { body, response } ) => {
887+ const parsed = parseBody ( getImagesBodySchema , body , response ) ;
888+ if ( 'error' in parsed ) return parsed . error ;
889+ const data = parsed . data ;
828890 let images = [ ] ;
829- if ( body . body . record ) {
830- for ( const record of body . body . record ) {
891+ if ( data . record ) {
892+ for ( const record of data . record ) {
831893 if ( this . options . attachFiles ) {
832894 images . push ( await this . options . attachFiles ( { record : record } ) ) ;
833895 }
@@ -843,15 +905,18 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
843905 server . endpoint ( {
844906 method : 'POST' ,
845907 path : `/plugin/${ this . pluginInstanceId } /update_fields` ,
846- handler : async ( { body, adminUser, headers } ) => {
908+ handler : async ( { body, adminUser, headers, response } ) => {
909+ const parsed = parseBody ( updateFieldsBodySchema , body , response ) ;
910+ if ( 'error' in parsed ) return parsed . error ;
911+ const data = parsed . data ;
847912 let isAllowedToSave : any = { ok : true , error : '' } ;
848913 if ( this . options . isAllowedToSave ) {
849914 isAllowedToSave = await this . options . isAllowedToSave ( { record : { } , adminUser : adminUser , resource : this . resourceConfig } ) ;
850915 }
851916 if ( isAllowedToSave . ok !== false ) {
852- const selectedIds = body . selectedIds || [ ] ;
853- const fieldsToUpdate = body . fields || { } ;
854- const saveImages = body . saveImages ;
917+ const selectedIds = data . selectedIds || [ ] ;
918+ const fieldsToUpdate = data . fields || { } ;
919+ const saveImages = data . saveImages ;
855920 const outputImageFields = [ ] ;
856921 if ( this . options . generateImages ) {
857922 for ( const [ key , value ] of Object . entries ( this . options . generateImages ) ) {
@@ -950,9 +1015,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
9501015 server . endpoint ( {
9511016 method : 'POST' ,
9521017 path : `/plugin/${ this . pluginInstanceId } /get_image_generation_prompts` ,
953- handler : async ( { body, headers } ) => {
954- const Id = body . recordId || [ ] ;
955- const customPrompt = body . customPrompt || null ;
1018+ handler : async ( { body, headers, response } ) => {
1019+ const parsed = parseBody ( getImageGenerationPromptsBodySchema , body , response ) ;
1020+ if ( 'error' in parsed ) return parsed . error ;
1021+ const data = parsed . data ;
1022+ const Id = data . recordId || [ ] ;
1023+ const customPrompt = data . customPrompt || null ;
9561024 const record = await this . adminforth . resource ( this . resourceConfig . resourceId ) . get ( [ Filters . EQ ( this . resourceConfig . columns . find ( c => c . primaryKey ) ?. name , Id ) ] ) ;
9571025 const compiledGenerationOptions = await this . compileGenerationFieldTemplates ( record , JSON . stringify ( { "prompt" : customPrompt } ) ) ;
9581026 return compiledGenerationOptions ;
@@ -976,8 +1044,11 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
9761044 server . endpoint ( {
9771045 method : 'POST' ,
9781046 path : `/plugin/${ this . pluginInstanceId } /create-job` ,
979- handler : async ( { body, adminUser, headers } ) => {
980- const { actionType, recordId, customPrompt, filterFilledFields, sessionIds } = body ;
1047+ handler : async ( { body, adminUser, headers, response } ) => {
1048+ const parsed = parseBody ( createJobBodySchema , body , response ) ;
1049+ if ( 'error' in parsed ) return parsed . error ;
1050+ const data = parsed . data ;
1051+ const { actionType, recordId, customPrompt, filterFilledFields, sessionIds } = data ;
9811052 if ( this . options . rateLimits ) {
9821053 if ( sessionIds && sessionIds . length > 0 ) {
9831054 for ( const sessionId of sessionIds ) {
@@ -1011,15 +1082,15 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10111082 this . analyze_image ( jobId , recordId , adminUser , headers , customPrompt , filterFilledFields ) ;
10121083 break ;
10131084 case 'regenerate_images' :
1014- if ( ! body . prompt || ! body . fieldName ) {
1085+ if ( ! data . prompt || ! data . fieldName ) {
10151086 jobs . set ( jobId , { status : "failed" , error : "Missing prompt or field name" } ) ;
10161087 break ;
10171088 }
1018- this . regenerateImage ( jobId , recordId , body . fieldName , body . prompt , adminUser , headers ) ;
1089+ this . regenerateImage ( jobId , recordId , data . fieldName , data . prompt , adminUser , headers ) ;
10191090 break ;
10201091 case 'regenerate_cell' :
1021- const fieldToRegenerate = body . fieldToRegenerate ;
1022- this . regenerateCell ( jobId , fieldToRegenerate , recordId , body . action , body . prompt ) ;
1092+ const fieldToRegenerate = data . fieldToRegenerate ;
1093+ this . regenerateCell ( jobId , fieldToRegenerate , recordId , data . action , data . prompt ) ;
10231094 break ;
10241095 default :
10251096 jobs . set ( jobId , { status : "failed" , error : "Unknown action type" } ) ;
@@ -1036,7 +1107,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10361107 method : 'POST' ,
10371108 path : `/plugin/${ this . pluginInstanceId } /get-job-status` ,
10381109 handler : async ( { body, adminUser, headers, response } ) => {
1039- const jobId = body . jobId ;
1110+ const parsed = parseBody ( jobStatusBodySchema , body , response ) ;
1111+ if ( 'error' in parsed ) return parsed . error ;
1112+ const data = parsed . data ;
1113+ const jobId = data . jobId ;
10401114 if ( ! jobId ) {
10411115 response . setStatus ( 400 ) ;
10421116
@@ -1073,8 +1147,11 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10731147 server . endpoint ( {
10741148 method : 'POST' ,
10751149 path : `/plugin/${ this . pluginInstanceId } /update-rate-limits` ,
1076- handler : async ( { body, adminUser, headers } ) => {
1077- const actionType = body . actionType ;
1150+ handler : async ( { body, adminUser, headers, response } ) => {
1151+ const parsed = parseBody ( updateRateLimitsBodySchema , body , response ) ;
1152+ if ( 'error' in parsed ) return parsed . error ;
1153+ const data = parsed . data ;
1154+ const actionType = data . actionType ;
10781155 const sessionId = randomUUID ( ) ;
10791156 this . sessionIds . add ( sessionId ) ;
10801157 if ( actionType === 'analyze' && this . options . rateLimits ?. fillFieldsFromImages ) {
@@ -1101,9 +1178,12 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
11011178 server . endpoint ( {
11021179 method : 'POST' ,
11031180 path : `/plugin/${ this . pluginInstanceId } /compile_old_image_link` ,
1104- handler : async ( { body, adminUser, headers } ) => {
1105- const image = body . image ;
1106- const columnName = body . columnName ;
1181+ handler : async ( { body, adminUser, headers, response } ) => {
1182+ const parsed = parseBody ( compileOldImageLinkBodySchema , body , response ) ;
1183+ if ( 'error' in parsed ) return parsed . error ;
1184+ const data = parsed . data ;
1185+ const image = data . image ;
1186+ const columnName = data . columnName ;
11071187 if ( ! image ) {
11081188 return { ok : false , error : "Can't find image url" } ;
11091189 }
@@ -1131,7 +1211,10 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
11311211 server . endpoint ( {
11321212 method : 'POST' ,
11331213 path : `/plugin/${ this . pluginInstanceId } /get_filtered_ids` ,
1134- handler : async ( { body, adminUser, headers, query, cookies, requestUrl } ) => {
1214+ handler : async ( { body, adminUser, headers, query, cookies, requestUrl, response } ) => {
1215+ const parsed = parseBody ( getFilteredIdsBodySchema , body , response ) ;
1216+ if ( 'error' in parsed ) return parsed . error ;
1217+ const data = parsed . data ;
11351218 const resource = this . resourceConfig ;
11361219
11371220 for ( const hook of resource . hooks ?. list ?. beforeDatasourceRequest || [ ] ) {
0 commit comments