From dd19362fff40bc4db60911dc3803c142e108b8a5 Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Thu, 7 May 2026 15:21:08 +0100 Subject: [PATCH 1/6] chore: add public API alerting Signed-off-by: Joana Maia --- .../api/public/middlewares/errorHandler.ts | 28 ++++++++++++++++++- services/libs/slack/src/channels.ts | 3 +- services/libs/slack/src/types.ts | 1 + 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/src/api/public/middlewares/errorHandler.ts b/backend/src/api/public/middlewares/errorHandler.ts index 4da3552876..3c4dabe4d8 100644 --- a/backend/src/api/public/middlewares/errorHandler.ts +++ b/backend/src/api/public/middlewares/errorHandler.ts @@ -4,7 +4,13 @@ import { UnauthorizedError as Auth0UnauthorizedError, } from 'express-oauth2-jwt-bearer' -import { HttpError, InsufficientScopeError, InternalError, UnauthorizedError } from '@crowd/common' +import { + ConflictError, + HttpError, + InsufficientScopeError, + InternalError, + UnauthorizedError, +} from '@crowd/common' import { SlackChannel, SlackPersona, sendSlackNotification } from '@crowd/slack' /** @@ -17,6 +23,26 @@ export const errorHandler: ErrorRequestHandler = ( res: Response, _next: NextFunction, ) => { + if (error instanceof ConflictError) { + sendSlackNotification( + SlackChannel.CDP_LFX_SELF_SERVE_ALERTS, + SlackPersona.WARNING_PROPAGATOR, + `Public API Conflict 409: ${req.method} ${req.url}`, + [ + { + title: 'Request', + text: `*Method:* \`${req.method}\`\n*URL:* \`${req.url}\``, + }, + { + title: 'Conflict', + text: `*Message:* ${error.message}`, + }, + ], + ) + res.status(error.status).json(error.toJSON()) + return + } + if (error instanceof HttpError) { res.status(error.status).json(error.toJSON()) return diff --git a/services/libs/slack/src/channels.ts b/services/libs/slack/src/channels.ts index 8aa8e26357..dc9bf208a2 100644 --- a/services/libs/slack/src/channels.ts +++ b/services/libs/slack/src/channels.ts @@ -4,7 +4,7 @@ import { SlackChannel, SlackChannelConfig } from './types' const log = getServiceLogger() -const CHANNEL_WEBHOOK_URLS: Record = { +const CHANNEL_WEBHOOK_URLS: Record = { [SlackChannel.CDP_ALERTS]: process.env.CDP_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.CDP_CRITICAL_ALERTS]: process.env.CDP_CRITICAL_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.CDP_DATA_QUALITY_ALERTS]: process.env.CDP_DATA_QUALITY_ALERTS_SLACK_WEBHOOK_URL, @@ -12,6 +12,7 @@ const CHANNEL_WEBHOOK_URLS: Record = { [SlackChannel.CDP_PROJECTS_ALERTS]: process.env.CDP_PROJECTS_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.INSIGHTS_ALERTS]: process.env.INSIGHTS_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.INSIGHTS_CRITICAL_ALERTS]: process.env.INSIGHTS_CRITICAL_ALERTS_SLACK_WEBHOOK_URL, + [SlackChannel.CDP_LFX_SELF_SERVE_ALERTS]: process.env.CDP_LFX_SELF_SERVE_ALERTS_SLACK_WEBHOOK_URL, } // Check for missing webhook URLs on initialization diff --git a/services/libs/slack/src/types.ts b/services/libs/slack/src/types.ts index 6fa29ac028..ff08dc14d5 100644 --- a/services/libs/slack/src/types.ts +++ b/services/libs/slack/src/types.ts @@ -6,6 +6,7 @@ export enum SlackChannel { CDP_PROJECTS_ALERTS = 'CDP_PROJECTS_ALERTS', INSIGHTS_ALERTS = 'INSIGHTS_ALERTS', INSIGHTS_CRITICAL_ALERTS = 'INSIGHTS_CRITICAL_ALERTS', + CDP_LFX_SELF_SERVE_ALERTS = 'CDP_LFX_SELF_SERVE_ALERTS', } export enum SlackPersona { From 728a7cce7284f70c84c8508c7394e29f454c46f5 Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Fri, 8 May 2026 10:04:24 +0100 Subject: [PATCH 2/6] fix: address PR comments Signed-off-by: Joana Maia --- .../src/api/public/middlewares/errorHandler.ts | 16 ++++++++++++++++ services/libs/slack/src/channels.ts | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/src/api/public/middlewares/errorHandler.ts b/backend/src/api/public/middlewares/errorHandler.ts index 3c4dabe4d8..a919bceae7 100644 --- a/backend/src/api/public/middlewares/errorHandler.ts +++ b/backend/src/api/public/middlewares/errorHandler.ts @@ -33,6 +33,22 @@ export const errorHandler: ErrorRequestHandler = ( title: 'Request', text: `*Method:* \`${req.method}\`\n*URL:* \`${req.url}\``, }, + ...(Object.keys(req.params).length > 0 + ? [ + { + title: 'Params', + text: `\`\`\`${JSON.stringify(req.params, null, 2)}\`\`\``, + }, + ] + : []), + ...(req.body && Object.keys(req.body).length > 0 + ? [ + { + title: 'Body', + text: `\`\`\`${JSON.stringify(req.body, null, 2).substring(0, 1500)}\`\`\``, + }, + ] + : []), { title: 'Conflict', text: `*Message:* ${error.message}`, diff --git a/services/libs/slack/src/channels.ts b/services/libs/slack/src/channels.ts index dc9bf208a2..0bcd30c1f7 100644 --- a/services/libs/slack/src/channels.ts +++ b/services/libs/slack/src/channels.ts @@ -4,7 +4,7 @@ import { SlackChannel, SlackChannelConfig } from './types' const log = getServiceLogger() -const CHANNEL_WEBHOOK_URLS: Record = { +const CHANNEL_WEBHOOK_URLS: Record = { [SlackChannel.CDP_ALERTS]: process.env.CDP_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.CDP_CRITICAL_ALERTS]: process.env.CDP_CRITICAL_ALERTS_SLACK_WEBHOOK_URL, [SlackChannel.CDP_DATA_QUALITY_ALERTS]: process.env.CDP_DATA_QUALITY_ALERTS_SLACK_WEBHOOK_URL, From d3f0eff7313a39ee88e93ebfc0519c952bb4914b Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Fri, 8 May 2026 10:23:03 +0100 Subject: [PATCH 3/6] fix: address PR comments Signed-off-by: Joana Maia --- .../src/api/public/middlewares/errorHandler.ts | 16 ---------------- .../src/api/public/v1/members/resolveMember.ts | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/backend/src/api/public/middlewares/errorHandler.ts b/backend/src/api/public/middlewares/errorHandler.ts index a919bceae7..3c4dabe4d8 100644 --- a/backend/src/api/public/middlewares/errorHandler.ts +++ b/backend/src/api/public/middlewares/errorHandler.ts @@ -33,22 +33,6 @@ export const errorHandler: ErrorRequestHandler = ( title: 'Request', text: `*Method:* \`${req.method}\`\n*URL:* \`${req.url}\``, }, - ...(Object.keys(req.params).length > 0 - ? [ - { - title: 'Params', - text: `\`\`\`${JSON.stringify(req.params, null, 2)}\`\`\``, - }, - ] - : []), - ...(req.body && Object.keys(req.body).length > 0 - ? [ - { - title: 'Body', - text: `\`\`\`${JSON.stringify(req.body, null, 2).substring(0, 1500)}\`\`\``, - }, - ] - : []), { title: 'Conflict', text: `*Message:* ${error.message}`, diff --git a/backend/src/api/public/v1/members/resolveMember.ts b/backend/src/api/public/v1/members/resolveMember.ts index 43d6e948a2..91999915c3 100644 --- a/backend/src/api/public/v1/members/resolveMember.ts +++ b/backend/src/api/public/v1/members/resolveMember.ts @@ -32,7 +32,7 @@ export async function resolveMemberByIdentities(req: Request, res: Response): Pr if (memberIds.length === 0) { throw new NotFoundError('Member not found') } else if (memberIds.length > 1) { - throw new ConflictError('Conflicting identities') + throw new ConflictError(`Conflicting identities for these users: ${memberIds.join(', ')}`) } const memberId = memberIds[0] From 2edd497d8edec977f9f344970a3576adbffabacf Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Fri, 8 May 2026 11:26:12 +0100 Subject: [PATCH 4/6] fix: lint error Signed-off-by: Joana Maia --- .../src/activities/member.ts | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/services/apps/members_enrichment_worker/src/activities/member.ts b/services/apps/members_enrichment_worker/src/activities/member.ts index 36225a1176..fe5564578a 100644 --- a/services/apps/members_enrichment_worker/src/activities/member.ts +++ b/services/apps/members_enrichment_worker/src/activities/member.ts @@ -77,14 +77,8 @@ export async function getIdentitiesExistInOtherMembers( excludeMemberId: string, identities: IMemberIdentity[], ): Promise { - let rows: IMemberIdentity[] = [] - - try { - const db = svc.postgres.reader - rows = await getIdentitiesExistInOthers(db, excludeMemberId, identities) - } catch (err) { - throw err - } + const db = svc.postgres.reader + const rows = await getIdentitiesExistInOthers(db, excludeMemberId, identities) return rows } @@ -94,25 +88,21 @@ export async function updateMemberWithEnrichmentData( identities: IMemberIdentity[], attributes?: IAttributes, ): Promise { - try { - await svc.postgres.writer.connection().tx(async (tx) => { - for (const identity of identities) { - await createMemberIdentity(new PgPromiseQueryExecutor(tx), { - memberId, - platform: identity.platform, - value: identity.value, - type: identity.type, - verified: identity.verified || false, - source: 'enrichment', - }) - } - if (attributes) { - await updateMemberAttributes(tx, memberId, attributes) - } - }) - } catch (err) { - throw err - } + await svc.postgres.writer.connection().tx(async (tx) => { + for (const identity of identities) { + await createMemberIdentity(new PgPromiseQueryExecutor(tx), { + memberId, + platform: identity.platform, + value: identity.value, + type: identity.type, + verified: identity.verified || false, + source: 'enrichment', + }) + } + if (attributes) { + await updateMemberAttributes(tx, memberId, attributes) + } + }) } export async function mergeMembers( From 724830b11f0a8251672723ebe384da112ed67481 Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Fri, 8 May 2026 11:44:24 +0100 Subject: [PATCH 5/6] chore: remove memberIds from message Signed-off-by: Joana Maia --- backend/src/api/public/middlewares/errorHandler.ts | 5 +++++ backend/src/api/public/v1/members/resolveMember.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/src/api/public/middlewares/errorHandler.ts b/backend/src/api/public/middlewares/errorHandler.ts index 3c4dabe4d8..c51088cc78 100644 --- a/backend/src/api/public/middlewares/errorHandler.ts +++ b/backend/src/api/public/middlewares/errorHandler.ts @@ -24,6 +24,8 @@ export const errorHandler: ErrorRequestHandler = ( _next: NextFunction, ) => { if (error instanceof ConflictError) { + const memberIds = (error as ConflictError & { memberIds?: string[] }).memberIds + req.log.warn({ memberIds }, 'Public API conflict') sendSlackNotification( SlackChannel.CDP_LFX_SELF_SERVE_ALERTS, SlackPersona.WARNING_PROPAGATOR, @@ -37,6 +39,9 @@ export const errorHandler: ErrorRequestHandler = ( title: 'Conflict', text: `*Message:* ${error.message}`, }, + ...(memberIds + ? [{ title: 'Member IDs', text: memberIds.join(', ') }] + : []), ], ) res.status(error.status).json(error.toJSON()) diff --git a/backend/src/api/public/v1/members/resolveMember.ts b/backend/src/api/public/v1/members/resolveMember.ts index 91999915c3..a41870925f 100644 --- a/backend/src/api/public/v1/members/resolveMember.ts +++ b/backend/src/api/public/v1/members/resolveMember.ts @@ -32,7 +32,13 @@ export async function resolveMemberByIdentities(req: Request, res: Response): Pr if (memberIds.length === 0) { throw new NotFoundError('Member not found') } else if (memberIds.length > 1) { - throw new ConflictError(`Conflicting identities for these users: ${memberIds.join(', ')}`) + const error = new ConflictError('Conflicting identities') + Object.defineProperty(error, 'memberIds', { + value: memberIds, + enumerable: false, + configurable: true, + }) + throw error } const memberId = memberIds[0] From 142d894d0c6b6570bf0ca8865ca277a1ade89717 Mon Sep 17 00:00:00 2001 From: Joana Maia Date: Fri, 8 May 2026 15:23:27 +0100 Subject: [PATCH 6/6] fix: lint Signed-off-by: Joana Maia --- backend/src/api/public/middlewares/errorHandler.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/api/public/middlewares/errorHandler.ts b/backend/src/api/public/middlewares/errorHandler.ts index c51088cc78..626807bad0 100644 --- a/backend/src/api/public/middlewares/errorHandler.ts +++ b/backend/src/api/public/middlewares/errorHandler.ts @@ -39,9 +39,7 @@ export const errorHandler: ErrorRequestHandler = ( title: 'Conflict', text: `*Message:* ${error.message}`, }, - ...(memberIds - ? [{ title: 'Member IDs', text: memberIds.join(', ') }] - : []), + ...(memberIds ? [{ title: 'Member IDs', text: memberIds.join(', ') }] : []), ], ) res.status(error.status).json(error.toJSON())