@@ -94,6 +94,8 @@ type GetResourceDataToolResponse = {
9494 options ?: Record < string , unknown > ;
9595} ;
9696
97+ type DateTimeColumnType = AdminForthDataTypes . DATETIME | AdminForthDataTypes . TIME ;
98+
9799const DEFAULT_USER_TIME_ZONE = 'UTC' ;
98100
99101const TOOL_OVERRIDES : Record < string , ToolOverride > = {
@@ -373,6 +375,7 @@ async function applyToolOverride(params: {
373375 adminUser,
374376 inputs : nestedInputs ,
375377 httpExtra : nestedHttpExtra ,
378+ userTimeZone : nestedUserTimeZone ,
376379 } ) ;
377380
378381 return applyToolOverride ( {
@@ -488,20 +491,104 @@ function createRawExpressResponse(response: ToolHttpResponse) {
488491 return rawResponse ;
489492}
490493
494+ function normalizeDateTimeInputsToUtc (
495+ body : Record < string , unknown > ,
496+ adminforth : IAdminForth ,
497+ userTimeZone ?: string ,
498+ ) : Record < string , unknown > {
499+ if ( ! userTimeZone || typeof body . resourceId !== 'string' ) {
500+ return body ;
501+ }
502+
503+ const resource = adminforth . config . resources . find ( ( res ) => res . resourceId === body . resourceId ) ;
504+
505+ if ( ! resource ) {
506+ return body ;
507+ }
508+
509+ const columnsByName = new Map ( resource . dataSourceColumns . map ( ( column ) => [ column . name , column ] ) ) ;
510+
511+ const normalizeColumnValue = (
512+ value : unknown ,
513+ columnType : DateTimeColumnType ,
514+ ) : unknown => {
515+ if ( Array . isArray ( value ) ) {
516+ return value . map ( ( item ) => normalizeColumnValue ( item , columnType ) ) ;
517+ }
518+
519+ if ( typeof value !== 'string' || value === '' ) {
520+ return value ;
521+ }
522+
523+ if ( columnType === AdminForthDataTypes . DATETIME ) {
524+ return dayjs . tz ( value , userTimeZone ) . utc ( ) . toISOString ( ) ;
525+ }
526+
527+ if ( columnType === AdminForthDataTypes . TIME ) {
528+ const userDate = dayjs ( ) . tz ( userTimeZone ) . format ( 'YYYY-MM-DD' ) ;
529+ return dayjs . tz ( `${ userDate } T${ value } ` , userTimeZone ) . utc ( ) . format ( 'HH:mm:ss' ) ;
530+ }
531+ } ;
532+
533+ const normalizeValue = ( value : unknown , key ?: string ) : unknown => {
534+ const column = key ? columnsByName . get ( key ) : undefined ;
535+
536+ if ( column ?. type === AdminForthDataTypes . DATETIME || column ?. type === AdminForthDataTypes . TIME ) {
537+ return normalizeColumnValue ( value , column . type ) ;
538+ }
539+
540+ if ( Array . isArray ( value ) ) {
541+ return value . map ( ( item ) => normalizeValue ( item ) ) ;
542+ }
543+
544+ if ( ! value || typeof value !== 'object' ) {
545+ return value ;
546+ }
547+
548+ const record = value as Record < string , unknown > ;
549+ const filterColumn = typeof record . field === 'string' ? columnsByName . get ( record . field ) : undefined ;
550+
551+ if (
552+ 'value' in record &&
553+ ( filterColumn ?. type === AdminForthDataTypes . DATETIME || filterColumn ?. type === AdminForthDataTypes . TIME )
554+ ) {
555+ return {
556+ ...record ,
557+ value : normalizeColumnValue ( record . value , filterColumn . type ) ,
558+ } ;
559+ }
560+
561+ return Object . fromEntries (
562+ Object . entries ( record ) . map ( ( [ nestedKey , nestedValue ] ) => [
563+ nestedKey ,
564+ normalizeValue ( nestedValue , nestedKey ) ,
565+ ] ) ,
566+ ) ;
567+ } ;
568+
569+ return normalizeValue ( body ) as Record < string , unknown > ;
570+ }
571+
491572async function callCapturedEndpoint ( params : {
492573 adminforth : IAdminForth ;
493574 adminUser ?: AdminUser ;
494575 endpoint : CapturedEndpoint ;
495576 httpExtra ?: Partial < HttpExtra > ;
496577 inputs ?: Record < string , unknown > ;
578+ userTimeZone ?: string ;
497579} ) {
498- const { adminforth, adminUser, endpoint, httpExtra, inputs } = params ;
580+ const { adminforth, adminUser, endpoint, httpExtra, inputs, userTimeZone } = params ;
499581 const response = createToolResponse ( httpExtra ?. response ) ;
500582 const headers = {
501583 'content-type' : 'application/json' ,
584+ 'X-TimeZone' : userTimeZone ,
502585 ...( httpExtra ?. headers ?? { } ) ,
503586 } ;
504- const body = ( inputs ?? httpExtra ?. body ?? { } ) as Record < string , unknown > ;
587+ const body = normalizeDateTimeInputsToUtc (
588+ ( inputs ?? httpExtra ?. body ?? { } ) as Record < string , unknown > ,
589+ adminforth ,
590+ headers [ 'X-TimeZone' ] ,
591+ ) ;
505592 const query = httpExtra ?. query ?? { } ;
506593 const cookies = normalizeCookies ( httpExtra ?. cookies ) ;
507594 const requestUrl = httpExtra ?. requestUrl ?? `${ adminforth . config . baseUrl } /adminapi/v1${ endpoint . path } ` ;
@@ -596,6 +683,7 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
596683 adminUser : adminUser ?? adminuser ,
597684 inputs,
598685 httpExtra,
686+ userTimeZone,
599687 } ) ;
600688
601689 const processedOutput = await applyToolOverride ( {
@@ -629,4 +717,4 @@ export function serializeApiBasedTool(tool: ApiBasedTool | undefined) {
629717 output_schema : tool . output_schema ,
630718 call : '[Function]' ,
631719 } ;
632- }
720+ }
0 commit comments