Skip to content

Commit ab76b6a

Browse files
authored
chore: improve readme & added get-linkedin-token script (NOJIRA) (#13)
1 parent 207dd82 commit ab76b6a

File tree

7 files changed

+175
-9
lines changed

7 files changed

+175
-9
lines changed

README.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,66 @@ _Note: This API is available only to LinkedIn users registered in the European U
88

99
# Environment variables
1010

11-
TBD
11+
Environment variables must be defined in the .env file. The full list of environment variables can be found in [environment.ts](./src/util/environment.ts). Below, we highlight the most important variables for running local tests:
12+
13+
- `LOCAL_LINKEDIN_API_TOKEN=YOUR_LINKEDIN_API_TOKEN`: Your LinkedIn user token for easily testing this Lambda locally.
14+
- `LOGGER_CONSOLE=true`: Enables the logger in a human-friendly format instead of JSON format.
15+
16+
## How to retrieve a valid Linkedin token
17+
18+
- `LOCAL_LINKEDIN_CLIENT_ID` and `LOCAL_LINKEDIN_CLIENT_SECRET` must be configured properly (see next section). Then type:
19+
20+
```bash
21+
yarn dev:token
22+
```
23+
24+
And copy token value inside `LOCAL_LINKEDIN_CLIENT_SECRET` environment variable.
1225

1326
# How to develop
1427

1528
Follow these steps to set up the development environment and run essential tasks for the project:
1629

30+
- Install all dependencies using `nvm` and `yarn`:
31+
32+
```bash
33+
nvm use
34+
yarn install
35+
```
36+
1737
- If you're part of Manfred's staff, download the necessary environment variables using [Doppler](https://www.doppler.com/):
1838

1939
```bash
20-
npm run dev:secrets
40+
yarn dev:secrets
2141
```
2242

23-
- Run the application locally with a fake sqs event. This uses the `LOCAL_PROFILE_API_TOKEN` environment variable to retrieve the LinkedIn profile:
43+
- Run the application locally with a fake sqs event. This uses the `LOCAL_PROFILE_API_TOKEN` environment variable to retrieve the LinkedIn profile (by default dev Manfred user):
2444

2545
```bash
26-
npm run dev
46+
yarn dev
47+
```
48+
49+
- If you need to get new API token:
50+
51+
```bash
52+
yarn dev:token
2753
```
2854

2955
- Automatically lint the code and apply fixes to linting and formatting errors:
3056

3157
```bash
32-
npm run lint
58+
yarn lint
3359
```
3460

3561
- Execute the unit test suite to ensure that everything is working as expected:
3662

3763
```bash
38-
npm run test
64+
yarn test
65+
```
66+
67+
- If you have `localstack` configured, you can send a real message to the queue, simulate its reception, and handle it with:
68+
69+
```bash
70+
yarn dev:consumer
3971
```
4072

4173
Make sure you have all necessary environment variables and dependencies set up before running the tasks.

jest.config.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,12 @@ module.exports = {
77
preset: 'ts-jest',
88
testEnvironment: 'node',
99
coverageReporters: ['lcov', 'text-summary', 'clover'],
10-
collectCoverageFrom: ['src/**/*.ts', '!src/test/**/*', '!src/**/*spec.ts', '!src/launch.ts', '!src/launch-sqs-consumer.ts']
10+
collectCoverageFrom: [
11+
'src/**/*.ts',
12+
'!src/test/**/*',
13+
'!src/**/*spec.ts',
14+
'!src/launch.ts',
15+
'!src/launch-sqs-consumer.ts',
16+
'!src/get-linkedin-token.ts'
17+
]
1118
};

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:token": "node --env-file=.env -r esbuild-register ./src/get-linkedin-token.ts",
1617
"dev:consumer": "node --env-file=.env -r esbuild-register ./src/launch-sqs-consumer.ts",
1718
"audit:critical": "npm audit --audit-level=critical",
1819
"clean": "rm -rf dist node_modules"
@@ -44,6 +45,7 @@
4445
"eslint-plugin-prettier": "5.2.1",
4546
"jest": "29.7.0",
4647
"jest-mock-extended": "3.0.7",
48+
"open": "10.1.0",
4749
"prettier": "3.4.2",
4850
"prettier-plugin-sort-imports": "1.8.6",
4951
"sqs-consumer": "11.2.0",

src/get-linkedin-token.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint-disable no-process-env */
2+
/* eslint-disable no-console */
3+
import axios from 'axios';
4+
import { createServer } from 'http';
5+
import open from 'open';
6+
7+
/**
8+
* 👉 Script: Retrieves Linkedin API token for local testing (LOCAL_LINKEDIN_API_TOKEN environment variable value)
9+
*/
10+
11+
const CLIENT_ID = process.env.LOCAL_LINKEDIN_CLIENT_ID!;
12+
const CLIENT_SECRET = process.env.LOCAL_LINKEDIN_CLIENT_SECRET!;
13+
const SCOPE = 'r_dma_portability_3rd_party';
14+
const REDIRECT_URI = 'http://localhost:3000/callback';
15+
16+
const startOAuthFlow = async (): Promise<void> => {
17+
const authUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}`;
18+
console.log('👉 Opening LinkedIn OAuth URL...');
19+
await open(authUrl);
20+
};
21+
22+
// Create a simple server to handle the LinkedIn redirect
23+
createServer(async (req, res) => {
24+
if (req.url?.startsWith('/callback')) {
25+
const urlParams = new URLSearchParams(req.url.split('?')[1]);
26+
const code = urlParams.get('code');
27+
28+
console.log('🔑 Authorization Code:', code);
29+
30+
if (!code) {
31+
res.writeHead(400, { 'Content-Type': 'text/plain' });
32+
res.end('Authorization code is missing.');
33+
return;
34+
}
35+
36+
try {
37+
const tokenUrl = 'https://www.linkedin.com/oauth/v2/accessToken';
38+
const tokenUrlParams = new URLSearchParams({
39+
grant_type: 'authorization_code',
40+
code,
41+
redirect_uri: REDIRECT_URI,
42+
client_id: CLIENT_ID!,
43+
client_secret: CLIENT_SECRET!
44+
}).toString();
45+
46+
const response = await axios.post(tokenUrl, tokenUrlParams, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
47+
const accessToken = response.data.access_token;
48+
49+
console.log('✅ Access Token:', accessToken);
50+
51+
res.writeHead(200, { 'Content-Type': 'text/plain' });
52+
res.end(`Access Token: ${accessToken}\n\nYou can now close this window.`);
53+
} catch (error: unknown) {
54+
console.error('❌ Error exchanging code for token:', error);
55+
res.writeHead(500, { 'Content-Type': 'text/plain' });
56+
res.end('Failed to get access token.');
57+
}
58+
}
59+
}).listen(3000, () => {
60+
console.log('🚀 Server running at http://localhost:3000');
61+
startOAuthFlow().catch((error) => {
62+
console.error('❌ Error starting OAuth flow:', error);
63+
});
64+
});

src/launch-sqs-consumer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { handler } from './index';
66
import { logger } from './util/logger';
77

88
/**
9-
* Test the SQS consumer locally using LocalStack queues
9+
* 👉 Script: Test the SQS consumer locally using LocalStack queues
1010
*/
1111
const sqsClient = new SQSClient({ region: 'eu-west-1', endpoint: 'http://localhost:4566' });
1212

src/launch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { createMockedLinkedinProfileRequest } from './test/mocks/linkedin-profil
55
import { createMockedSqsSEvent } from './test/mocks/sqs.mocks';
66
import { logger } from './util/logger';
77

8-
// 👉 Fake manual execution without AWS: For local debug
8+
/**
9+
* 👉 Script: Fake manual execution without AWS: For local debug
10+
*/
911
void (async (): Promise<void> => {
1012
const linkedinApiToken = process.env['LOCAL_LINKEDIN_API_TOKEN'];
1113
const request = createMockedLinkedinProfileRequest({ linkedinApiToken });

yarn.lock

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,13 @@ [email protected]:
20952095
ieee754 "^1.1.4"
20962096
isarray "^1.0.0"
20972097

2098+
bundle-name@^4.1.0:
2099+
version "4.1.0"
2100+
resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889"
2101+
integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==
2102+
dependencies:
2103+
run-applescript "^7.0.0"
2104+
20982105
call-bind-apply-helpers@^1.0.0:
20992106
version "1.0.1"
21002107
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
@@ -2308,6 +2315,19 @@ deepmerge@^4.2.2:
23082315
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
23092316
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
23102317

2318+
default-browser-id@^5.0.0:
2319+
version "5.0.0"
2320+
resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26"
2321+
integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==
2322+
2323+
default-browser@^5.2.1:
2324+
version "5.2.1"
2325+
resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf"
2326+
integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==
2327+
dependencies:
2328+
bundle-name "^4.1.0"
2329+
default-browser-id "^5.0.0"
2330+
23112331
define-data-property@^1.1.4:
23122332
version "1.1.4"
23132333
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
@@ -2317,6 +2337,11 @@ define-data-property@^1.1.4:
23172337
es-errors "^1.3.0"
23182338
gopd "^1.0.1"
23192339

2340+
define-lazy-prop@^3.0.0:
2341+
version "3.0.0"
2342+
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f"
2343+
integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==
2344+
23202345
delayed-stream@~1.0.0:
23212346
version "1.0.0"
23222347
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -2947,6 +2972,11 @@ is-core-module@^2.13.0:
29472972
dependencies:
29482973
hasown "^2.0.2"
29492974

2975+
is-docker@^3.0.0:
2976+
version "3.0.0"
2977+
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200"
2978+
integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==
2979+
29502980
is-extglob@^2.1.1:
29512981
version "2.1.1"
29522982
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -2976,6 +3006,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
29763006
dependencies:
29773007
is-extglob "^2.1.1"
29783008

3009+
is-inside-container@^1.0.0:
3010+
version "1.0.0"
3011+
resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
3012+
integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==
3013+
dependencies:
3014+
is-docker "^3.0.0"
3015+
29793016
is-number@^7.0.0:
29803017
version "7.0.0"
29813018
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
@@ -2993,6 +3030,13 @@ is-typed-array@^1.1.3:
29933030
dependencies:
29943031
which-typed-array "^1.1.14"
29953032

3033+
is-wsl@^3.1.0:
3034+
version "3.1.0"
3035+
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2"
3036+
integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==
3037+
dependencies:
3038+
is-inside-container "^1.0.0"
3039+
29963040
isarray@^1.0.0:
29973041
version "1.0.0"
29983042
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -3697,6 +3741,16 @@ onetime@^5.1.2:
36973741
dependencies:
36983742
mimic-fn "^2.1.0"
36993743

3744+
3745+
version "10.1.0"
3746+
resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1"
3747+
integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==
3748+
dependencies:
3749+
default-browser "^5.2.1"
3750+
define-lazy-prop "^3.0.0"
3751+
is-inside-container "^1.0.0"
3752+
is-wsl "^3.1.0"
3753+
37003754
optionator@^0.9.3:
37013755
version "0.9.4"
37023756
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
@@ -3937,6 +3991,11 @@ reusify@^1.0.4:
39373991
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
39383992
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
39393993

3994+
run-applescript@^7.0.0:
3995+
version "7.0.0"
3996+
resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb"
3997+
integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==
3998+
39403999
run-parallel@^1.1.9:
39414000
version "1.2.0"
39424001
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"

0 commit comments

Comments
 (0)