Skip to content

Commit 10b4097

Browse files
committed
fix: add some sqs request model fixes
1 parent 737bb71 commit 10b4097

File tree

10 files changed

+2815
-25
lines changed

10 files changed

+2815
-25
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"test": "jest --silent --logHeapUsage --coverage",
1414
"dev": "node --env-file=.env -r esbuild-register ./src/launch.ts -e .ts",
1515
"dev:secrets": "doppler setup --no-interactive && doppler secrets download --no-file --format env > .env && touch .env.overrides && cat .env.overrides >> .env",
16+
"dev:consumer": "node --env-file=.env -r esbuild-register ./src/launch-sqs-consumer.ts",
1617
"audit:critical": "npm audit --audit-level=critical",
1718
"clean": "rm -rf dist node_modules"
1819
},
@@ -42,6 +43,7 @@
4243
"jest-mock-extended": "4.0.0-beta1",
4344
"prettier": "3.3.3",
4445
"prettier-plugin-sort-imports": "1.8.6",
46+
"sqs-consumer": "11.2.0",
4547
"typescript": "5.6.3"
4648
},
4749
"engines": {
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { IsIn, IsInt, IsNumber, IsString, IsUrl, Min } from 'class-validator';
1+
import { IsIn, IsInt, IsNumber, IsString, IsUrl } from 'class-validator';
22

33
export class LinkedinProfileRequest {
44
@IsString()
55
public messageId!: string;
66

7+
@IsString()
8+
public importId!: string;
9+
710
@IsString()
811
public contextId!: string;
912

@@ -14,13 +17,10 @@ export class LinkedinProfileRequest {
1417
@IsInt()
1518
public profileId!: number;
1619

20+
// Profile parameters
1721
@IsString()
18-
public profileApiToken!: string;
22+
public linkedinApiToken!: string;
1923

2024
@IsUrl()
21-
public linkedinUrl!: string;
22-
23-
@IsInt()
24-
@Min(1)
25-
public attempt!: number;
25+
public linkedinProfileUrl!: string;
2626
}

src/index.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ describe('Linkedin lambda handler', () => {
4646

4747
it('should throw an error if message is not a valid LinkedinProfileRequest', async () => {
4848
const request = createMockedLinkedinProfileRequest();
49-
request.profileApiToken = undefined as unknown as string; // missing required field
49+
request.linkedinApiToken = undefined as unknown as string; // missing required field
5050
const event = createMockedSqsSEvent(request);
5151
const expectedErrorString =
52-
'[LinkedinProfileRequestMapper] Validation failed: ["property: profileApiToken errors: profileApiToken must be a string"]';
52+
'[LinkedinProfileRequestMapper] Validation failed: ["property: linkedinApiToken errors: linkedinApiToken must be a string"]';
5353

5454
await expect(handler(event, {} as Context, () => {})).rejects.toThrow(new Error(expectedErrorString));
5555
});

src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ export const handler: Handler = async (event: SQSEvent): Promise<LinkedinProfile
1111
try {
1212
const linkedinProfileRequest = LinkedinProfileRequestMapper.toDomain(event);
1313
Environment.setupEnvironment(linkedinProfileRequest);
14-
logger.info(`⌛️ [handler] Starting Linkedin profile request for linkedinUrl: ${linkedinProfileRequest.linkedinUrl}`, linkedinProfileRequest);
14+
logger.info(
15+
`⌛️ [handler] Starting Linkedin profile request for linkedinProfileUrl: ${linkedinProfileRequest.linkedinProfileUrl}`,
16+
linkedinProfileRequest
17+
);
1518

16-
const { linkedinProfile, isEmptyProfile } = await new LinkedinProfileService().getLinkedinProfile(linkedinProfileRequest.profileApiToken);
19+
const { linkedinProfile, isEmptyProfile } = await new LinkedinProfileService().getLinkedinProfile(linkedinProfileRequest.linkedinApiToken);
1720

1821
if (isEmptyProfile) {
19-
logger.warn(`👻 [handler] Linkedin profile is not synced for linkedinUrl: ${linkedinProfileRequest.linkedinUrl}`, linkedinProfileRequest);
22+
logger.warn(
23+
`👻 [handler] Linkedin profile is not synced for linkedinProfileUrl: ${linkedinProfileRequest.linkedinProfileUrl}`,
24+
linkedinProfileRequest
25+
);
2026
// TODO: send response to SQS queue again if no max retries
2127
return undefined;
2228
}
@@ -27,7 +33,7 @@ export const handler: Handler = async (event: SQSEvent): Promise<LinkedinProfile
2733

2834
return linkedinProfileResponse;
2935
} catch (error) {
30-
logger.error(`❌ [handler] Error processing Linkedin profile request: ${error}`, error);
36+
logger.error(`❌ [handler] Error processing Linkedin profile request`, { error, event });
3137
throw error;
3238
}
3339
};

src/launch-sqs-consumer.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Message, SQSClient } from '@aws-sdk/client-sqs';
2+
import { Context, SQSEvent, SQSRecord } from 'aws-lambda';
3+
import { Consumer } from 'sqs-consumer';
4+
5+
import { handler } from './index';
6+
import { logger } from './util/logger';
7+
8+
/**
9+
* Test the SQS consumer locally using LocalStack queues
10+
*/
11+
const sqsClient = new SQSClient({ region: 'eu-west-1', endpoint: 'http://localhost:4566' });
12+
13+
const queueUrl = 'http://localhost:4566/000000000000/linkedin-api-import-profile-local.fifo';
14+
15+
const consumer = Consumer.create({
16+
queueUrl,
17+
handleMessage: async (message: Message) => {
18+
logger.info('👉 Message received:', message);
19+
20+
try {
21+
const sqsRecord = {
22+
messageId: message.MessageId!,
23+
receiptHandle: message.ReceiptHandle!,
24+
body: message.Body!,
25+
attributes: message.Attributes || {},
26+
messageAttributes: message.MessageAttributes || {},
27+
md5OfBody: message.MD5OfBody || '',
28+
eventSource: 'aws:sqs',
29+
eventSourceARN: `arn:aws:sqs:eu-west-1:000000000000:linkedin-api-import-profile-local.fifo`,
30+
awsRegion: 'eu-west-1'
31+
} as unknown as SQSRecord;
32+
33+
const event = { Records: [sqsRecord] } as SQSEvent;
34+
35+
const response = await handler(event, {} as Context, () => {
36+
logger.info('✅ Executed handler');
37+
});
38+
39+
return response;
40+
} catch (error) {
41+
logger.error('❌ Error handling message:', error);
42+
}
43+
},
44+
sqs: sqsClient
45+
});
46+
47+
consumer.on('error', (err: Error) => {
48+
logger.error('❌ Consumer error:', err.message);
49+
});
50+
51+
consumer.on('processing_error', (err: Error) => {
52+
logger.error('❌ Processing error:', err.message);
53+
});
54+
55+
consumer.on('empty', () => {
56+
logger.info('🥱 Waiting for messages...');
57+
});
58+
59+
logger.info(`🔄 Starting SQS consumer for ${queueUrl}...`);
60+
consumer.start();

src/launch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { logger } from './util/logger';
77

88
// 👉 Fake manual execution without AWS: For local debug
99
void (async (): Promise<void> => {
10-
const profileApiToken = process.env['LOCAL_PROFILE_API_TOKEN'];
11-
const request = createMockedLinkedinProfileRequest({ profileApiToken });
10+
const linkedinApiToken = process.env['LOCAL_LINKEDIN_API_TOKEN'];
11+
const request = createMockedLinkedinProfileRequest({ linkedinApiToken });
1212
const fakeEvent = createMockedSqsSEvent(request);
1313

1414
logger.info('👉 [launch.ts] Debugging lambda handler ...');

src/mappers/linkedin-profile.request.mapper.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ export class LinkedinProfileRequestMapper {
1010

1111
const request = new LinkedinProfileRequest();
1212
request.messageId = message.messageId;
13+
request.importId = messageBody.importId;
1314
request.contextId = messageBody.contextId;
1415
request.env = messageBody.env;
15-
request.profileId = messageBody.profileId;
16-
request.profileApiToken = messageBody.profileApiToken;
17-
request.linkedinUrl = messageBody.linkedinUrl;
18-
request.attempt = messageBody.attempt;
16+
request.profileId = +messageBody.profileId;
17+
request.linkedinApiToken = messageBody.linkedinApiToken;
18+
request.linkedinProfileUrl = messageBody.linkedinProfileUrl;
1919

2020
this.validate(request);
2121

src/test/mocks/linkedin-profile.mocks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { LinkedinProfile } from '../../domain/linkedin-profile';
44
export const createMockedLinkedinProfileRequest = (customValues: Partial<LinkedinProfileRequest> = {}): LinkedinProfileRequest => {
55
const request = new LinkedinProfileRequest();
66
request.messageId = customValues.messageId ?? '059f36b4-87a3-44ab-83d2-661975830a7d';
7+
request.importId = customValues.importId ?? '1';
78
request.contextId = customValues.contextId ?? '1234';
89
request.env = customValues.env ?? 'local';
910
request.profileId = customValues.profileId ?? 123;
10-
request.profileApiToken = customValues.profileApiToken ?? 'fake-token';
11-
request.linkedinUrl = customValues.linkedinUrl ?? 'https://www.linkedin.com/in/username';
12-
request.attempt = customValues.attempt ?? 1;
11+
request.linkedinApiToken = customValues.linkedinApiToken ?? 'fake-token';
12+
request.linkedinProfileUrl = customValues.linkedinProfileUrl ?? 'https://www.linkedin.com/in/username';
1313
return request;
1414
};
1515

src/util/environment.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import { setContextLogger } from './logger';
66

77
export class Environment {
88
public readonly LOGGER_CONSOLE: boolean;
9-
public readonly LOCAL_PROFILE_API_TOKEN?: string;
9+
public readonly LOCAL_LINKEDIN_API_TOKEN?: string;
1010

1111
private constructor(_envName: EnvironmentType) {
1212
const env = cleanEnv(process.env, {
1313
LOGGER_CONSOLE: bool({ default: false }),
14-
LOCAL_PROFILE_API_TOKEN: str({ default: undefined })
14+
LOCAL_LINKEDIN_API_TOKEN: str({ default: undefined })
1515
});
1616

1717
this.LOGGER_CONSOLE = env.LOGGER_CONSOLE;
18-
this.LOCAL_PROFILE_API_TOKEN = env.LOCAL_PROFILE_API_TOKEN ?? undefined;
18+
this.LOCAL_LINKEDIN_API_TOKEN = env.LOCAL_LINKEDIN_API_TOKEN ?? undefined;
1919
}
2020

2121
public static setupEnvironment(request: LinkedinProfileRequest): Environment {

0 commit comments

Comments
 (0)