@@ -56,26 +56,26 @@ export class UserElasticsearchService implements OnModuleInit {
5656 mobile_country_code : { type : 'keyword' } ,
5757 gender : { type : 'keyword' } ,
5858 dob : { type : 'date' , null_value : null } ,
59- address : { type : 'text' } ,
60- state : { type : 'keyword' } ,
61- district : { type : 'keyword' } ,
6259 country : { type : 'keyword' } ,
63- pincode : { type : 'keyword' } ,
64- status : { type : 'keyword' } ,
6560 customFields : {
6661 type : 'nested' ,
6762 properties : {
63+ // NOTE: We intentionally expose only the fields needed for search.
64+ // Any additional metadata coming from DB (fieldValuesId, context, state,
65+ // fieldParams, etc.) will either be stripped before indexing or stored
66+ // but *not* indexed.
6867 fieldId : { type : 'keyword' } ,
69- fieldValuesId : { type : 'keyword' } ,
70- fieldname : { type : 'text' } ,
7168 code : { type : 'keyword' } , // Always treat as string
7269 label : { type : 'text' } ,
7370 type : { type : 'keyword' } ,
7471 value : { type : 'text' } , // Always treat as string for flexibility
75- context : { type : 'keyword' } ,
76- contextType : { type : 'keyword' } ,
77- state : { type : 'keyword' } ,
78- fieldParams : { type : 'object' , dynamic : true } ,
72+ // Store fieldParams in _source only (any shape is accepted)
73+ // and do NOT index it so it can be any array/object without
74+ // causing mapping conflicts.
75+ fieldParams : {
76+ type : 'object' ,
77+ enabled : false ,
78+ } ,
7979 } ,
8080 } ,
8181 } ,
@@ -166,28 +166,14 @@ export class UserElasticsearchService implements OnModuleInit {
166166 }
167167
168168 // Ensure all required fields are present
169+ const applications = user . applications || [ ] ;
169170 const elasticUser : IUser = {
170171 userId : user . userId ,
171- profile : {
172- userId : user . profile . userId ,
173- username : user . profile . username ,
174- firstName : user . profile . firstName ,
175- lastName : user . profile . lastName ,
176- middleName : user . profile . middleName ,
177- email : user . profile . email ,
178- mobile : user . profile . mobile ,
179- mobile_country_code : user . profile . mobile_country_code ,
180- gender : user . profile . gender ,
181- dob : user . profile . dob ,
182- country : user . profile . country ,
183- address : user . profile . address ,
184- district : user . profile . district ,
185- state : user . profile . state ,
186- pincode : user . profile . pincode ,
187- status : user . profile . status ,
188- customFields : user . profile . customFields || { } ,
189- } ,
190- applications : user . applications || [ ] ,
172+ profile : this . normalizeProfileForElasticsearch (
173+ user . profile ,
174+ applications
175+ ) ,
176+ applications,
191177 courses : user . courses || [ ] ,
192178 createdAt : user . createdAt ,
193179 updatedAt : user . updatedAt ,
@@ -221,7 +207,10 @@ export class UserElasticsearchService implements OnModuleInit {
221207 ) : Promise < any > {
222208 try {
223209 // Try to update the profile field in Elasticsearch
224- return await this . updateUser ( userId , { doc : { profile } } ) ;
210+ const normalizedProfile = this . normalizeProfileForElasticsearch ( profile ) ;
211+ return await this . updateUser ( userId , {
212+ doc : { profile : normalizedProfile } ,
213+ } ) ;
225214 } catch ( error : any ) {
226215 // If the document is missing, create it from DB
227216 if (
@@ -241,6 +230,105 @@ export class UserElasticsearchService implements OnModuleInit {
241230 }
242231 }
243232
233+ /**
234+ * Normalize profile object before indexing/updating in Elasticsearch.
235+ * Ensures `customFields` match the compact shape expected in the index
236+ * and that internal DB-only fields (fieldValuesId, context, state, etc.)
237+ * or heavy metadata (fieldParams.options) are not used for search.
238+ */
239+ private normalizeProfileForElasticsearch (
240+ profile : any ,
241+ applications ?: any [ ]
242+ ) : any {
243+ if ( ! profile ) {
244+ return profile ;
245+ }
246+
247+ const normalized : any = {
248+ userId : profile . userId ,
249+ username : profile . username ,
250+ firstName : profile . firstName ,
251+ lastName : profile . lastName ,
252+ middleName : profile . middleName ,
253+ email : profile . email ,
254+ mobile : profile . mobile ,
255+ mobile_country_code : profile . mobile_country_code ,
256+ gender : profile . gender ,
257+ dob : profile . dob ,
258+ country : profile . country ,
259+ status : profile . status ,
260+ } ;
261+
262+ const rawCustomFields = profile . customFields || [ ] ;
263+
264+ // Build a set of fieldIds that are clearly part of application forms,
265+ // based on the application.progress.pages[*].fields maps. Any customField
266+ // whose fieldId appears here will be treated as an application field
267+ // and excluded from profile.customFields.
268+ const applicationFieldIds = new Set < string > ( ) ;
269+ if ( Array . isArray ( applications ) ) {
270+ for ( const app of applications ) {
271+ const pages = app ?. progress ?. pages || { } ;
272+ for ( const page of Object . values ( pages ) ) {
273+ const fields = ( page as any ) ?. fields || { } ;
274+ for ( const fieldId of Object . keys ( fields ) ) {
275+ if ( fieldId ) {
276+ applicationFieldIds . add ( fieldId ) ;
277+ }
278+ }
279+ }
280+ }
281+ }
282+
283+ normalized . customFields = Array . isArray ( rawCustomFields )
284+ ? rawCustomFields
285+ . filter ( ( field : any ) => {
286+ if ( ! field ) return false ;
287+ const fieldId =
288+ field . fieldId ?? field . fieldid ?? field . id ?? undefined ;
289+
290+ // If context is present and is not USERS, treat this as a non-profile
291+ // field (e.g., form/application field) and exclude it from
292+ // profile.customFields. This ensures that only true profile-level
293+ // custom fields remain under profile, even when applications array
294+ // is empty or not yet populated.
295+ const context = field . context ?? field . contextType ;
296+ if ( context && context !== 'USERS' ) {
297+ return false ;
298+ }
299+
300+ // If this fieldId is used inside any application.progress page,
301+ // we consider it an application field and do NOT keep it under
302+ // profile.customFields.
303+ if ( fieldId && applicationFieldIds . has ( String ( fieldId ) ) ) {
304+ return false ;
305+ }
306+ return true ;
307+ } )
308+ . map ( ( field : any ) => {
309+ if ( ! field ) {
310+ return field ;
311+ }
312+
313+ const fieldId = field . fieldId ?? field . fieldid ?? field . id ;
314+ const label = field . label ?? field . fieldname ?? field . name ?? '' ;
315+ const value = field . value ?? '' ;
316+ const code = field . code ?? value ?? '' ;
317+ const type = field . type ?? null ;
318+
319+ return {
320+ fieldId,
321+ code,
322+ label,
323+ type,
324+ value,
325+ } ;
326+ } )
327+ : [ ] ;
328+
329+ return normalized ;
330+ }
331+
244332 /**
245333 * Update user document in Elasticsearch. If missing, create from DB.
246334 * @param userId
@@ -580,14 +668,17 @@ export class UserElasticsearchService implements OnModuleInit {
580668 // Check if value is an array (multiple countries)
581669 if ( Array . isArray ( value ) && value . length > 0 ) {
582670 const countries = value
583- . map ( v => String ( v ) . trim ( ) )
584- . filter ( v => v . length > 0 ) ;
671+ . map ( ( v ) => String ( v ) . trim ( ) )
672+ . filter ( ( v ) => v . length > 0 ) ;
585673 if ( countries . length > 0 ) {
586674 searchQuery . bool . filter . push ( {
587675 bool : {
588- should : countries . map ( c => ( {
676+ should : countries . map ( ( c ) => ( {
589677 term : {
590- [ `profile.${ field } ` ] : { value : c , case_insensitive : true } ,
678+ [ `profile.${ field } ` ] : {
679+ value : c ,
680+ case_insensitive : true ,
681+ } ,
591682 } ,
592683 } ) ) ,
593684 minimum_should_match : 1 ,
@@ -599,7 +690,10 @@ export class UserElasticsearchService implements OnModuleInit {
599690 if ( country . length > 0 ) {
600691 searchQuery . bool . filter . push ( {
601692 term : {
602- [ `profile.${ field } ` ] : { value : country , case_insensitive : true } ,
693+ [ `profile.${ field } ` ] : {
694+ value : country ,
695+ case_insensitive : true ,
696+ } ,
603697 } ,
604698 } ) ;
605699 }
0 commit comments