Skip to content

Commit 6d5e083

Browse files
authored
Merge pull request #557 from Tusharmahajan12/new_aspmsep24
Fixed ES issue due to application fields and Sync API created
2 parents 9fa8fc5 + e302783 commit 6d5e083

6 files changed

Lines changed: 175 additions & 68 deletions

File tree

src/adapters/postgres/user-adapter.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,10 +1747,6 @@ export class PostgresUserService implements IServicelocator {
17471747
? result.dob.toISOString()
17481748
: result.dob ?? '',
17491749
country: result.country || '',
1750-
address: result.address || '',
1751-
state: result.state || '',
1752-
district: result.district || '',
1753-
pincode: result.pincode || '',
17541750
status: result.status,
17551751
customFields: elasticCustomFields,
17561752
},
@@ -2993,21 +2989,18 @@ export class PostgresUserService implements IServicelocator {
29932989
dob: formattedDob,
29942990
country: user.country,
29952991
gender: user.gender,
2996-
address: user.address || '',
2997-
district: user.district || '',
2998-
state: user.state || '',
2999-
pincode: user.pincode || '',
30002992
status: user.status,
30012993
customFields, // Now filtered to exclude form schema fields
30022994
};
30032995

3004-
// Upsert (update or create) the user profile in Elasticsearch
2996+
// Upsert (update or create) the user profile in Elasticsearch.
30052997
if (isElasticsearchEnabled()) {
30062998
await this.userElasticsearchService.updateUserProfile(
30072999
user.userId,
30083000
profile,
30093001
async (userId: string) => {
3010-
// Fetch the latest user from the database for upsert
3002+
// Fallback: fetch latest user and profile-only data from DB.
3003+
// Applications will be added later by form-submission flows.
30113004
const dbUser = await this.usersRepository.findOne({
30123005
where: { userId },
30133006
});
@@ -3033,10 +3026,6 @@ export class PostgresUserService implements IServicelocator {
30333026
dob: formattedDob,
30343027
gender: dbUser.gender,
30353028
country: dbUser.country || '',
3036-
address: dbUser.address || '',
3037-
district: dbUser.district || '',
3038-
state: dbUser.state || '',
3039-
pincode: dbUser.pincode || '',
30403029
status: dbUser.status,
30413030
customFields,
30423031
},

src/elasticsearch/elasticsearch.service.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,6 @@ export class ElasticsearchService {
185185
gender: source?.profile?.gender || '',
186186
dob:source?.profile?.dob || '',
187187
country:source?.profile?.country || '',
188-
address: source?.profile?.address || '',
189-
district: source?.profile?.district || '',
190-
state: source?.profile?.state || '',
191-
pincode: source?.profile?.pincode || '',
192188
status: source?.profile?.status || 'active',
193189
customFields: source?.profile?.customFields || [],
194190
},
@@ -264,10 +260,6 @@ export class ElasticsearchService {
264260
gender: "",
265261
dob: null,
266262
country:'',
267-
address: '',
268-
district: '',
269-
state: '',
270-
pincode: '',
271263
status: 'active',
272264
customFields: [],
273265
};

src/elasticsearch/interfaces/user.interface.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,8 @@ export interface IProfile {
5050
mobile_country_code: string;
5151
gender: string;
5252
dob: string;
53-
country:string;
54-
address: string;
55-
district: string;
56-
state: string;
57-
pincode: string;
53+
country: string;
54+
5855
status: string;
5956
customFields: Record<string, any>;
6057
}

src/elasticsearch/user-elasticsearch.service.ts

Lines changed: 131 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/forms/controllers/form-submission.controller.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,23 @@ export class FormSubmissionController {
137137
) {
138138
return this.formSubmissionService.remove(id, mode);
139139
}
140+
141+
/**
142+
* Sync a user's full Elasticsearch document (profile + profile.customFields + applications)
143+
* from the database. This is useful when:
144+
* 1. The ES document exists but is missing some data (e.g., applications),
145+
* 2. The ES document is completely missing for the given userId.
146+
*/
147+
@Post('elasticsearch/sync/:userId')
148+
@UseFilters(new AllExceptionsFilter(APIID.FORM_SUBMISSION_UPDATE))
149+
@ApiOperation({
150+
summary:
151+
'Sync full user document (profile + customFields + applications) into Elasticsearch',
152+
})
153+
@ApiParam({ name: 'userId', type: String })
154+
async syncUserToElasticsearch(
155+
@Param('userId') userId: string,
156+
) {
157+
return this.formSubmissionService.syncUserToElasticsearch(userId);
158+
}
140159
}

src/forms/services/form-submission.service.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,26 @@ export class FormSubmissionService {
273273
}
274274
}
275275

276+
/**
277+
* Sync a user's full document (profile + applications) into Elasticsearch.
278+
* - If the ES document exists but is partial, it will be overwritten with the
279+
* full document from the database.
280+
* - If the ES document is missing, it will be created from the database.
281+
*/
282+
public async syncUserToElasticsearch(userId: string): Promise<IUser> {
283+
const userDoc = await this.buildUserDocumentForElasticsearch(userId);
284+
if (!userDoc) {
285+
throw new BadRequestException(`User with ID ${userId} not found`);
286+
}
287+
288+
if (!isElasticsearchEnabled()) {
289+
return userDoc;
290+
}
291+
292+
await this.userElasticsearchService.createUser(userDoc);
293+
return userDoc;
294+
}
295+
276296
private async buildWhereClause(
277297
filters: FormSubmissionFilters,
278298
formSubmissionKeys: string[]
@@ -3153,10 +3173,6 @@ export class FormSubmissionService {
31533173
gender: user.gender,
31543174
dob: user.dob instanceof Date ? user.dob.toISOString() : user.dob || '',
31553175
country: user.country,
3156-
address: user.address || '',
3157-
district: user.district || '',
3158-
state: user.state || '',
3159-
pincode: user.pincode || '',
31603176
status: user.status,
31613177
customFields: profileCustomFields, // Only user profile custom fields
31623178
},

0 commit comments

Comments
 (0)