@@ -3,17 +3,26 @@ import '../src/config';
33import { parseArgs } from 'node:util' ;
44import { z } from 'zod' ;
55import 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' ;
88import { 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 */
1523const 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