From e675458ca9fbb5155867d8b83aa24dd3cd1f16cd Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Thu, 2 Apr 2026 14:42:13 +0530 Subject: [PATCH 1/4] PM-4490 Add names and contact in bulk lookup --- .circleci/config.yml | 2 +- sql/reports/identity/users-by-handles.sql | 15 +++++++++++++++ src/reports/identity/dtos/identity-users.dto.ts | 3 +++ .../identity/identity-reports.controller.ts | 2 +- src/reports/identity/identity-reports.service.ts | 6 ++++++ src/reports/report-directory.data.ts | 2 +- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d065519..25f7c72 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,7 +66,7 @@ workflows: - develop - pm-1127_1 - PM-4305 - - PM-4491-fix + - PM-4490 # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/sql/reports/identity/users-by-handles.sql b/sql/reports/identity/users-by-handles.sql index 6e547e5..be1985c 100644 --- a/sql/reports/identity/users-by-handles.sql +++ b/sql/reports/identity/users-by-handles.sql @@ -7,6 +7,9 @@ WITH input_handles AS ( SELECT ih.handle_input AS "handle", u.user_id AS "userId", + NULLIF(TRIM(mem."firstName"), '') AS "firstName", + NULLIF(TRIM(mem."lastName"), '') AS "lastName", + NULLIF(TRIM(ph.phone_number::text), '') AS "contactNumber", pe.address AS "email", COALESCE( NULLIF(BTRIM(mem."competitionCountryCode"), ''), @@ -24,6 +27,18 @@ LEFT JOIN LATERAL ( LIMIT 1 ) AS pe ON TRUE +LEFT JOIN LATERAL ( + SELECT p."number" AS phone_number + FROM members."memberPhone" AS p + WHERE p."userId" = u.user_id + AND NULLIF(TRIM(p."number"::text), '') IS NOT NULL + ORDER BY + (p."type" = 'HOME') DESC, + p."createdAt" DESC NULLS LAST, + p."id" ASC + LIMIT 1 +) AS ph + ON TRUE LEFT JOIN members."member" AS mem ON mem."userId" = u.user_id ORDER BY ih.ordinality; diff --git a/src/reports/identity/dtos/identity-users.dto.ts b/src/reports/identity/dtos/identity-users.dto.ts index cc7ddf6..70d2465 100644 --- a/src/reports/identity/dtos/identity-users.dto.ts +++ b/src/reports/identity/dtos/identity-users.dto.ts @@ -45,6 +45,9 @@ export class UsersByGroupQueryDto { export interface IdentityUserDto { userId: number | null; handle: string; + firstName: string | null; + lastName: string | null; + contactNumber: string | null; email: string | null; country: string | null; } diff --git a/src/reports/identity/identity-reports.controller.ts b/src/reports/identity/identity-reports.controller.ts index a009e76..a4692e0 100644 --- a/src/reports/identity/identity-reports.controller.ts +++ b/src/reports/identity/identity-reports.controller.ts @@ -90,7 +90,7 @@ export class IdentityReportsController { @ApiBearerAuth() @ApiOperation({ summary: - "Export user details (ID, handle, email, country) for a list of handles", + "Export user details (ID, handle, firstName, lastName, contactNumber, email, country) for a list of handles", }) @ApiConsumes("application/json", "multipart/form-data") @ApiBody({ diff --git a/src/reports/identity/identity-reports.service.ts b/src/reports/identity/identity-reports.service.ts index e9976b4..9521b1f 100644 --- a/src/reports/identity/identity-reports.service.ts +++ b/src/reports/identity/identity-reports.service.ts @@ -16,6 +16,9 @@ const SUPPORTED_HANDLES_UPLOAD_EXTENSIONS = new Set([".txt", ".csv"]); type UsersByHandlesRow = { userId: number | null; handle: string; + firstName: string | null; + lastName: string | null; + contactNumber: string | null; email: string | null; country: string | null; }; @@ -114,6 +117,9 @@ export class IdentityReportsService { return results.map((row) => ({ userId: row.userId, handle: row.handle, + firstName: row.firstName, + lastName: row.lastName, + contactNumber: row.contactNumber, email: row.email, country: alpha3ToCountryName(row.country) ?? row.country, })); diff --git a/src/reports/report-directory.data.ts b/src/reports/report-directory.data.ts index 5e91355..f72c6e5 100644 --- a/src/reports/report-directory.data.ts +++ b/src/reports/report-directory.data.ts @@ -431,7 +431,7 @@ const REGISTERED_REPORTS_DIRECTORY: RegisteredReportsDirectory = { identityPostReport( "Users by Handles", "/identity/users-by-handles", - "Export user ID, handle, email, and country for each supplied handle; unknown handles return empty fields", + "Export user ID, handle, firstName, lastName, contactNumber, email, and country for each supplied handle; unknown handles return empty fields", AppScopes.Identity.UsersByHandles, [handlesBodyParam], ), From 83ccabf3555e46bec05df3abe1d999cac4a3d254 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 14 Apr 2026 08:46:11 +0300 Subject: [PATCH 2/4] PM-3497 - fix user country & wins --- src/reports/member/member-search.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 93d2ab6..eddb68d 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -81,7 +81,7 @@ skill_event_stats AS ( SELECT se.user_id, se.skill_id, - COUNT(*) FILTER (WHERE LOWER(set_t.name) = 'challenge_win') AS wins, + COUNT(*) FILTER (WHERE LOWER(set_t.name) IN ('challenge_win', 'challenge_2nd_place', 'challenge_3rd_place')) AS wins, COUNT(*) AS submitted FROM skills.skill_event se JOIN skills.skill_event_type set_t ON set_t.id = se.skill_event_type_id @@ -235,7 +235,7 @@ SELECT COALESCE(m."availableForGigs", false) AS "openToWork", TRIM( COALESCE(maddr.city || ' ', '') || - COALESCE(m.country, COALESCE(m."competitionCountryCode", COALESCE(m."homeCountryCode", ''))) + COALESCE(m."homeCountryCode", COALESCE(m.country, COALESCE(m."competitionCountryCode", ''))) ) AS location, ${matchedSkillsExpr} AS "matchedSkills", ${matchIndexExpr} AS "matchIndex" From 3f22e0bd7fa30a6341f47497f5abcb96e4e9884d Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 14 Apr 2026 08:48:58 +0300 Subject: [PATCH 3/4] lint --- src/reports/member/member-search.controller.ts | 16 ++++++++++++++-- src/reports/member/member-search.service.spec.ts | 12 +++++++++--- src/reports/member/member-search.service.ts | 6 +++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/reports/member/member-search.controller.ts b/src/reports/member/member-search.controller.ts index 6c42b0c..5702a98 100644 --- a/src/reports/member/member-search.controller.ts +++ b/src/reports/member/member-search.controller.ts @@ -1,5 +1,17 @@ -import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from "@nestjs/common"; -import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { + Body, + Controller, + HttpCode, + HttpStatus, + Post, + UseGuards, +} from "@nestjs/common"; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from "@nestjs/swagger"; import { MemberSearchBodyDto } from "./dto/member-search.dto"; import { MemberSearchResponseDto } from "./dto/member-search-response.dto"; import { MemberSearchService } from "./member-search.service"; diff --git a/src/reports/member/member-search.service.spec.ts b/src/reports/member/member-search.service.spec.ts index 13dca55..faf35c6 100644 --- a/src/reports/member/member-search.service.spec.ts +++ b/src/reports/member/member-search.service.spec.ts @@ -56,7 +56,9 @@ describe("MemberSearchService", () => { expect(dataSql).toContain("WITH recently_active AS"); expect(dataSql).not.toContain("requested_skills AS"); - expect(dataSql).toContain('ORDER BY "matchIndex" DESC NULLS LAST, m.handle ASC'); + expect(dataSql).toContain( + 'ORDER BY "matchIndex" DESC NULLS LAST, m.handle ASC', + ); expect(countSql).toContain("SELECT COUNT(*)::integer AS total"); @@ -85,7 +87,9 @@ describe("MemberSearchService", () => { }); it("uses filter params for count query but excludes pagination params", async () => { - mockDbService.query.mockResolvedValueOnce([]).mockResolvedValueOnce([{ total: 0 }]); + mockDbService.query + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ total: 0 }]); await service.search({ country: "us", @@ -99,7 +103,9 @@ describe("MemberSearchService", () => { const dataParams = mockDbService.query.mock.calls[0][1] as unknown[]; const countParams = mockDbService.query.mock.calls[1][1] as unknown[]; - expect(dataSql).toContain('ORDER BY m.handle ASC, "matchIndex" DESC NULLS LAST'); + expect(dataSql).toContain( + 'ORDER BY m.handle ASC, "matchIndex" DESC NULLS LAST', + ); expect(dataParams).toEqual(["us", 5, 5]); expect(countParams).toEqual(["us"]); }); diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index eddb68d..778d4f8 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -182,17 +182,17 @@ member_address AS ( // ------------------------------------------------- dynamic WHERE const where: string[] = [`m.status = 'ACTIVE'`]; - if (openToWork === true) { + if (typeof openToWork === "boolean") { where.push(`m."availableForGigs" = true`); } - if (recentlyActive === true) { + if (typeof recentlyActive === "boolean") { where.push( `EXISTS (SELECT 1 FROM recently_active ra WHERE ra.user_id = m."userId")`, ); } - if (verifiedProfile === true) { + if (typeof verifiedProfile === "boolean") { where.push( `(COALESCE(m.verified, false) = true OR EXISTS (SELECT 1 FROM verified_via_trolley vt WHERE vt.user_id = m."userId"))`, ); From 7a119621e6d20ce8be4a5491762b64cce005c22b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 14 Apr 2026 10:50:23 +0300 Subject: [PATCH 4/4] Engagement type --- src/reports/member/member-search.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reports/member/member-search.service.ts b/src/reports/member/member-search.service.ts index 778d4f8..2470f8a 100644 --- a/src/reports/member/member-search.service.ts +++ b/src/reports/member/member-search.service.ts @@ -81,10 +81,13 @@ skill_event_stats AS ( SELECT se.user_id, se.skill_id, - COUNT(*) FILTER (WHERE LOWER(set_t.name) IN ('challenge_win', 'challenge_2nd_place', 'challenge_3rd_place')) AS wins, + COUNT(*) FILTER ( + WHERE LOWER(set_t.name) IN ('challenge_win', 'challenge_2nd_place', 'challenge_3rd_place', 'gig_completion') OR sest.name='engagement' + ) AS wins, COUNT(*) AS submitted FROM skills.skill_event se JOIN skills.skill_event_type set_t ON set_t.id = se.skill_event_type_id + JOIN skills.source_type sest ON sest.id = se.source_type_id WHERE se.skill_id = ANY(${pSkillIds}::uuid[]) GROUP BY se.user_id, se.skill_id ),