Skip to content

Commit 6e1386b

Browse files
authored
feat: backfill cv to profile (#3301)
1 parent 17c49e6 commit 6e1386b

File tree

8 files changed

+300
-63
lines changed

8 files changed

+300
-63
lines changed

__tests__/common/profile/import.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ describe('UserExperienceType work import', () => {
5959
verified: false,
6060
createdAt: expect.any(Date),
6161
updatedAt: expect.any(Date),
62+
flags: {},
6263
});
6364
const skills = await con
6465
.getRepository(UserExperienceSkill)
@@ -96,6 +97,7 @@ describe('UserExperienceType work import', () => {
9697
updatedAt: expect.any(Date),
9798
userId: 'user-work-2',
9899
verified: false,
100+
flags: {},
99101
});
100102
});
101103
});
@@ -131,6 +133,7 @@ describe('UserExperienceType education import', () => {
131133
grade: null,
132134
createdAt: expect.any(Date),
133135
updatedAt: expect.any(Date),
136+
flags: {},
134137
});
135138
});
136139

@@ -163,6 +166,7 @@ describe('UserExperienceType education import', () => {
163166
grade: null,
164167
createdAt: expect.any(Date),
165168
updatedAt: expect.any(Date),
169+
flags: {},
166170
});
167171
});
168172
});
@@ -200,6 +204,7 @@ describe('UserExperienceType certification import', () => {
200204
url: null,
201205
createdAt: expect.any(Date),
202206
updatedAt: expect.any(Date),
207+
flags: {},
203208
});
204209
});
205210

@@ -235,6 +240,7 @@ describe('UserExperienceType certification import', () => {
235240
url: null,
236241
createdAt: expect.any(Date),
237242
updatedAt: expect.any(Date),
243+
flags: {},
238244
});
239245
});
240246
});
@@ -273,6 +279,7 @@ describe('UserExperienceType project import', () => {
273279
url: null,
274280
createdAt: expect.any(Date),
275281
updatedAt: expect.any(Date),
282+
flags: {},
276283
});
277284
expect(skills.map((s) => s.value).sort()).toEqual(
278285
['GraphQL', 'Node.js'].sort(),
@@ -308,6 +315,7 @@ describe('UserExperienceType project import', () => {
308315
url: null,
309316
createdAt: expect.any(Date),
310317
updatedAt: expect.any(Date),
318+
flags: {},
311319
});
312320
});
313321
});

bin/importProfileFromJSON.ts

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@ import '../src/config';
33
import { parseArgs } from 'node:util';
44
import { z } from 'zod';
55
import createOrGetConnection from '../src/db';
6-
import { type DataSource } from 'typeorm';
7-
import { readFile } from 'node:fs/promises';
6+
import { QueryFailedError, type DataSource } from 'typeorm';
7+
import { readFile, stat, readdir } from 'node:fs/promises';
88
import { importUserExperienceFromJSON } from '../src/common/profile/import';
9+
import path from 'node:path';
10+
import { randomUUID } from 'node:crypto';
911

1012
/**
1113
* Import profile from JSON to user by id
1214
*
13-
* npx ts-node bin/importProfileFromJSON.ts --path ~/Downloads/testuser.json -u testuser
15+
* Single file usage:
16+
*
17+
* npx ts-node bin/importProfileFromJSON.ts --path ~/Downloads/testuser.json
18+
*
19+
* Directory usage:
20+
*
21+
* npx ts-node bin/importProfileFromJSON.ts --path ~/Downloads/profiles --limit 100 --offset 0 --import import_run_test
1422
*/
1523
const main = async () => {
1624
let con: DataSource | null = null;
25+
let failedImports = 0;
1726

1827
try {
1928
const { values } = parseArgs({
@@ -22,28 +31,103 @@ const main = async () => {
2231
type: 'string',
2332
short: 'p',
2433
},
25-
userId: {
34+
limit: {
35+
type: 'string',
36+
short: 'l',
37+
},
38+
offset: {
39+
type: 'string',
40+
short: 'o',
41+
},
42+
uid: {
2643
type: 'string',
27-
short: 'u',
2844
},
2945
},
3046
});
3147

3248
const paramsSchema = z.object({
3349
path: z.string().nonempty(),
34-
userId: z.string().nonempty(),
50+
limit: z.coerce.number().int().positive().default(10),
51+
offset: z.coerce.number().int().positive().default(0),
52+
uid: z.string().nonempty().default(randomUUID()),
3553
});
3654

3755
const params = paramsSchema.parse(values);
3856

57+
console.log(`Starting import with ID: ${params.uid}`);
58+
3959
con = await createOrGetConnection();
4060

41-
const dataJSON = JSON.parse(await readFile(params.path, 'utf-8'));
61+
const pathStat = await stat(params.path);
62+
63+
let filePaths = [params.path];
64+
65+
if (pathStat.isDirectory()) {
66+
filePaths = await readdir(params.path, 'utf-8');
67+
}
68+
69+
filePaths.sort(); // ensure consistent order for offset/limit
4270

43-
await importUserExperienceFromJSON({
44-
con: con.manager,
45-
dataJson: dataJSON,
46-
userId: params.userId,
71+
console.log(`Found files: ${filePaths.length}`);
72+
73+
console.log(
74+
`Importing: ${Math.min(params.limit, filePaths.length)} (limit ${params.limit}, offset ${params.offset})`,
75+
);
76+
77+
await con.transaction(async (entityManager) => {
78+
for (const [index, fileName] of filePaths
79+
.slice(params.offset, params.offset + params.limit)
80+
.entries()) {
81+
const filePath =
82+
params.path === fileName
83+
? fileName
84+
: path.join(params.path, fileName);
85+
86+
try {
87+
if (!filePath.endsWith('.json')) {
88+
throw { type: 'not_json_ext', filePath };
89+
}
90+
91+
const userId = filePath.split('/').pop()?.split('.json')[0];
92+
93+
if (!userId) {
94+
throw { type: 'no_user_id', filePath };
95+
}
96+
97+
const dataJSON = JSON.parse(await readFile(filePath, 'utf-8'));
98+
99+
await importUserExperienceFromJSON({
100+
con: entityManager,
101+
dataJson: dataJSON,
102+
userId,
103+
importId: params.uid,
104+
});
105+
} catch (error) {
106+
failedImports += 1;
107+
108+
if (error instanceof QueryFailedError) {
109+
console.error({
110+
type: 'db_query_failed',
111+
message: error.message,
112+
query: error.query,
113+
filePath,
114+
});
115+
} else if (error instanceof z.ZodError) {
116+
console.error({
117+
type: 'zod_error',
118+
message: error.issues[0].message,
119+
path: error.issues[0].path,
120+
filePath,
121+
});
122+
} else {
123+
console.error(error);
124+
}
125+
}
126+
127+
if (index && index % 100 === 0) {
128+
console.log(`Done so far: ${index}, failed: ${failedImports}`);
129+
}
130+
}
47131
});
48132
} catch (error) {
49133
console.error(error instanceof z.ZodError ? z.prettifyError(error) : error);
@@ -52,6 +136,12 @@ const main = async () => {
52136
con.destroy();
53137
}
54138

139+
if (failedImports > 0) {
140+
console.log(`Failed imports: ${failedImports}`);
141+
} else {
142+
console.log('Done!');
143+
}
144+
55145
process.exit(0);
56146
}
57147
};

0 commit comments

Comments
 (0)