@@ -4,6 +4,7 @@ import { map } from 'rxjs/operators';
44import { MinioService } from '../minio.service' ;
55import { Socket } from 'net' ;
66import { IncomingMessage , ServerResponse } from 'http' ;
7+ import { MINIO_FILE_FIELD_METADATA } from '../constants' ;
78
89@Injectable ( )
910export class FileUrlTransformInterceptor implements NestInterceptor {
@@ -34,7 +35,7 @@ export class FileUrlTransformInterceptor implements NestInterceptor {
3435 * @returns The transformed data with URLs replaced by presigned URLs.
3536 */
3637 // eslint-disable-next-line @typescript-eslint/no-explicit-any
37- private async transformUrls ( data : any ) : Promise < any > {
38+ private async transformUrls ( data : any , visited = new WeakSet < object > ( ) ) : Promise < any > {
3839 if ( ! data ) return data ;
3940
4041 // Skip processing for Node.js internal objects (HTTP, Socket, etc.)
@@ -47,6 +48,13 @@ export class FileUrlTransformInterceptor implements NestInterceptor {
4748 return data ;
4849 }
4950
51+ if ( typeof data === 'object' && data !== null ) {
52+ if ( visited . has ( data ) ) {
53+ return data ;
54+ }
55+ visited . add ( data ) ;
56+ }
57+
5058 // If it's a mongoose document, convert to plain object
5159 const obj = data . toJSON ? data . toJSON ( ) : data ;
5260
@@ -60,14 +68,19 @@ export class FileUrlTransformInterceptor implements NestInterceptor {
6068
6169 // Process each property recursively
6270 for ( const [ key , value ] of Object . entries ( obj ) ) {
63- // Check if this field is decorated with FileSchemaField
64- const isFileField = schema ?. paths ?. [ key ] ?. options ?. isFileField ;
71+ // Check if this field is decorated with FileSchemaField or FileColumn
72+ const isFileField =
73+ schema ?. paths ?. [ key ] ?. options ?. isFileField ||
74+ this . hasFileFieldMetadata ( data , key ) ||
75+ this . hasFileFieldMetadata ( obj , key ) ;
6576
66- if ( isFileField && typeof value === 'string' && value . includes ( '/' ) ) {
67- const [ bucketName , ...pathParts ] = value . split ( '/' ) ;
68- if ( pathParts . length > 0 ) {
77+ const inferredPath = ! isFileField ? this . extractMinioPath ( value ) : null ;
78+
79+ if ( ( isFileField || inferredPath ) && typeof value === 'string' ) {
80+ const split = inferredPath ?? this . splitBucketAndObject ( value ) ;
81+ if ( split ) {
6982 try {
70- obj [ key ] = await this . minioService . getPresignedUrl ( bucketName , pathParts . join ( '/' ) ) ;
83+ obj [ key ] = await this . minioService . getPresignedUrl ( split . bucketName , split . objectName ) ;
7184 } catch ( error ) {
7285 this . logger . error ( `Error generating presigned URL for ${ key } :` , error ) ;
7386 }
@@ -77,21 +90,59 @@ export class FileUrlTransformInterceptor implements NestInterceptor {
7790 else if (
7891 value &&
7992 typeof value === 'object' &&
80- ! Array . isArray ( value ) &&
81- Object . getPrototypeOf ( value ) === Object . prototype
93+ ! Array . isArray ( value )
8294 ) {
83- obj [ key ] = await this . transformUrls ( value ) ;
95+ obj [ key ] = await this . transformUrls ( value , visited ) ;
8496 }
8597 // Handle arrays of objects recursively
8698 else if ( Array . isArray ( value ) ) {
8799 obj [ key ] = await Promise . all (
88100 value . map ( ( item ) =>
89- typeof item === 'object' && item !== null ? this . transformUrls ( item ) : item ,
101+ typeof item === 'object' && item !== null ? this . transformUrls ( item , visited ) : item ,
90102 ) ,
91103 ) ;
92104 }
93105 }
94106
95107 return obj ;
96108 }
109+
110+ private hasFileFieldMetadata ( target : unknown , propertyKey : string ) : boolean {
111+ if ( ! target ) return false ;
112+
113+ const directMetadata = Reflect . getMetadata ( MINIO_FILE_FIELD_METADATA , target , propertyKey ) ;
114+ if ( directMetadata ) {
115+ return true ;
116+ }
117+
118+ const prototype = typeof target === 'object' ? Object . getPrototypeOf ( target ) : undefined ;
119+ if ( ! prototype ) {
120+ return false ;
121+ }
122+
123+ return Boolean ( Reflect . getMetadata ( MINIO_FILE_FIELD_METADATA , prototype , propertyKey ) ) ;
124+ }
125+
126+ private extractMinioPath ( value : unknown ) : { bucketName : string ; objectName : string } | null {
127+ if ( typeof value !== 'string' ) {
128+ return null ;
129+ }
130+
131+ const split = this . splitBucketAndObject ( value ) ;
132+ return split ;
133+ }
134+
135+ private splitBucketAndObject ( value : string ) : { bucketName : string ; objectName : string } | null {
136+ if ( ! value . includes ( '/' ) ) {
137+ return null ;
138+ }
139+ const [ bucketName , ...pathParts ] = value . split ( '/' ) ;
140+ if ( ! bucketName || pathParts . length === 0 ) {
141+ return null ;
142+ }
143+ return {
144+ bucketName,
145+ objectName : pathParts . join ( '/' ) ,
146+ } ;
147+ }
97148}
0 commit comments