@@ -2257,6 +2257,182 @@ async function importElasticSearchMember (filename, dateFilter = null) {
22572257 console . log ( `Finished reading the file: ${ filename } \n` )
22582258}
22592259
2260+ /**
2261+ * Import only the Dynamo basic_info traits and upsert them.
2262+ * @param {String } filename filename
2263+ * @param {Date|null } [dateFilter=null] optional date filter threshold
2264+ */
2265+ async function importDynamoBasicInfoTraits ( filename , dateFilter = null ) {
2266+ const traitFilePath = path . join ( MIGRATE_DIR , filename )
2267+
2268+ const lineCount = await countFileLines ( traitFilePath )
2269+ console . log ( `${ filename } has ${ lineCount } lines in total` )
2270+
2271+ const rlRead = readline . createInterface ( {
2272+ input : fs . createReadStream ( traitFilePath ) ,
2273+ crlfDelay : Infinity
2274+ } )
2275+
2276+ let currentLine = 0
2277+ let count = 0
2278+ let processed = 0
2279+ let skipped = 0
2280+ let errors = 0
2281+ let stringObject = ''
2282+
2283+ const processBasicInfoRecord = async ( dataItem ) => {
2284+ if ( dataItem . traitId !== 'basic_info' ) {
2285+ return
2286+ }
2287+
2288+ if ( ! shouldProcessRecord ( dataItem , dateFilter ) ) {
2289+ skipped += 1
2290+ return
2291+ }
2292+
2293+ let traitsPayload = dataItem . traits
2294+ if ( typeof traitsPayload === 'string' ) {
2295+ try {
2296+ traitsPayload = JSON . parse ( traitsPayload )
2297+ } catch ( err ) {
2298+ errors += 1
2299+ logWarn ( 'Skipping basic_info trait due to invalid JSON payload' , {
2300+ userId : dataItem . userId ,
2301+ error : err ?. message
2302+ } )
2303+ return
2304+ }
2305+ }
2306+
2307+ const traitEntries = Array . isArray ( traitsPayload ?. data ) ? traitsPayload . data : [ ]
2308+ if ( ! traitEntries . length ) {
2309+ skipped += 1
2310+ return
2311+ }
2312+
2313+ const normalizedEntries = [ ]
2314+ let targetUserId = normalizeUserId ( dataItem . userId )
2315+
2316+ for ( const rawEntry of traitEntries ) {
2317+ const normalizedEntry = pick ( rawEntry || { } , TRAIT_BASIC_INFO )
2318+ const normalizedUserId = normalizeUserId ( normalizedEntry . userId || targetUserId )
2319+ if ( ! normalizedUserId ) {
2320+ continue
2321+ }
2322+ targetUserId = targetUserId || normalizedUserId
2323+ normalizedEntries . push ( {
2324+ userId : normalizedUserId ,
2325+ country : normalizedEntry . country || '' ,
2326+ primaryInterestInTopcoder : normalizedEntry . primaryInterestInTopcoder || '' ,
2327+ tshirtSize : normalizedEntry . tshirtSize || null ,
2328+ gender : normalizedEntry . gender || null ,
2329+ shortBio : normalizedEntry . shortBio || '' ,
2330+ birthDate : _convert2Date ( normalizedEntry . birthDate ) ,
2331+ currentLocation : normalizedEntry . currentLocation || null ,
2332+ createdBy : CREATED_BY
2333+ } )
2334+ }
2335+
2336+ if ( normalizedEntries . length === 0 || ! targetUserId ) {
2337+ skipped += 1
2338+ return
2339+ }
2340+
2341+ const payload = {
2342+ userId : targetUserId ,
2343+ memberTraits : {
2344+ basicInfo : normalizedEntries . map ( entry => ( {
2345+ ...entry ,
2346+ userId : targetUserId
2347+ } ) )
2348+ }
2349+ }
2350+
2351+ try {
2352+ const updated = await updateMembersWithTraitsAndSkills ( payload )
2353+ if ( updated ) {
2354+ processed += 1
2355+ } else {
2356+ errors += 1
2357+ logWarn ( 'Basic info trait update returned falsy result' , { userId : targetUserId } )
2358+ }
2359+ } catch ( err ) {
2360+ errors += 1
2361+ logError ( 'Failed to upsert basic_info trait' , { userId : targetUserId , error : err ?. message } )
2362+ }
2363+ }
2364+
2365+ for await ( const line of rlRead ) {
2366+ currentLine += 1
2367+ if ( currentLine % 10 === 0 ) {
2368+ const percentage = ( ( currentLine / lineCount ) * 100 ) . toFixed ( 2 )
2369+ process . stdout . clearLine ( )
2370+ process . stdout . cursorTo ( 0 )
2371+ process . stdout . write ( `Migrate Progress: ${ percentage } %, read ${ count } , processed ${ processed } , skipped ${ skipped } , errors ${ errors } ` )
2372+ }
2373+
2374+ let trimmedLine = line . trim ( )
2375+ if ( ! trimmedLine || trimmedLine === ',' || trimmedLine === '[' || trimmedLine === ']' || trimmedLine === '],' ) {
2376+ continue
2377+ }
2378+
2379+ if ( trimmedLine . startsWith ( '[' ) ) {
2380+ trimmedLine = trimmedLine . substring ( 1 ) . trim ( )
2381+ if ( ! trimmedLine ) {
2382+ continue
2383+ }
2384+ }
2385+ if ( trimmedLine . endsWith ( ']' ) ) {
2386+ trimmedLine = trimmedLine . substring ( 0 , trimmedLine . length - 1 ) . trim ( )
2387+ if ( ! trimmedLine ) {
2388+ continue
2389+ }
2390+ }
2391+
2392+ if ( ! stringObject ) {
2393+ stringObject = trimmedLine
2394+ } else {
2395+ stringObject += trimmedLine
2396+ }
2397+
2398+ let jsonCandidate = stringObject
2399+ if ( jsonCandidate . endsWith ( ',' ) ) {
2400+ jsonCandidate = jsonCandidate . slice ( 0 , - 1 )
2401+ }
2402+
2403+ let dataItem
2404+ try {
2405+ dataItem = JSON . parse ( jsonCandidate )
2406+ } catch ( err ) {
2407+ continue
2408+ }
2409+
2410+ stringObject = ''
2411+ count += 1
2412+ await processBasicInfoRecord ( dataItem )
2413+ }
2414+
2415+ if ( stringObject ) {
2416+ let jsonCandidate = stringObject
2417+ if ( jsonCandidate . endsWith ( ',' ) ) {
2418+ jsonCandidate = jsonCandidate . slice ( 0 , - 1 )
2419+ }
2420+ if ( jsonCandidate ) {
2421+ try {
2422+ const dataItem = JSON . parse ( jsonCandidate )
2423+ count += 1
2424+ await processBasicInfoRecord ( dataItem )
2425+ } catch ( err ) {
2426+ errors += 1
2427+ logWarn ( 'Skipping trailing basic_info trait due to invalid JSON payload' , { error : err ?. message } )
2428+ }
2429+ }
2430+ }
2431+
2432+ console . log ( `\nProcessed ${ processed } basic_info traits, skipped ${ skipped } , errors ${ errors } ` )
2433+ console . log ( `Finished reading the file: ${ filename } \n` )
2434+ }
2435+
22602436/**
22612437 * Update member status values from ElasticSearch snapshot
22622438 * @param {String } filename filename
@@ -2412,9 +2588,12 @@ async function fixMemberUpdateData (memberItem, dbItem) {
24122588 }
24132589 } else if ( memberItem . traits . traitId === 'basic_info' ) {
24142590 const traitData = pick ( memberItem . traits . data [ 0 ] , TRAIT_BASIC_INFO )
2415- if ( traitData . userId && traitData . country && traitData . primaryInterestInTopcoder && traitData . shortBio ) {
2591+ if ( traitData . userId ) {
24162592 memberItemUpdate . memberTraits . basicInfo = [ {
24172593 ...traitData ,
2594+ country : traitData . country || '' ,
2595+ primaryInterestInTopcoder : traitData . primaryInterestInTopcoder || '' ,
2596+ shortBio : traitData . shortBio || '' ,
24182597 birthDate : _convert2Date ( traitData . birthDate )
24192598 } ]
24202599 }
@@ -3714,7 +3893,7 @@ async function runMigrationStep (step, dateFilter, askQuestion) {
37143893 migrationRuntimeState . dateFilter = dateFilter
37153894 destructiveApprovals . delete ( step )
37163895
3717- const shouldRunIntegrityCheck = [ '1' , '2' , '3' , '4' , '5' , '7' ] . includes ( step )
3896+ const shouldRunIntegrityCheck = [ '1' , '2' , '3' , '4' , '5' , '7' , '8' ] . includes ( step )
37183897
37193898 try {
37203899 if ( shouldRunIntegrityCheck ) {
@@ -3842,6 +4021,11 @@ async function runMigrationStep (step, dateFilter, askQuestion) {
38424021 await updateMemberStatusFromElasticSearch ( memberElasticsearchFilename , dateFilter )
38434022 break
38444023 }
4024+ case '8' : {
4025+ const memberTraitFilename = 'MemberProfileTrait.json'
4026+ await importDynamoBasicInfoTraits ( memberTraitFilename , dateFilter )
4027+ break
4028+ }
38454029 default :
38464030 throw new Error ( `Unsupported step "${ step } "` )
38474031 }
@@ -3878,6 +4062,7 @@ async function main () {
38784062 console . log ( '5. Import Dynamo MemberStatHistory' )
38794063 console . log ( '6. Import Distribution Stats' )
38804064 console . log ( '7. Update ElasticSearch Member Status' )
4065+ console . log ( '8. Import Dynamo Basic Info Traits' )
38814066 console . log ( '' )
38824067 console . log ( 'Destructive clears require --full-reset or ALLOW_DESTRUCTIVE=true and an explicit confirmation. Incremental runs retain existing data by default.' )
38834068
@@ -3890,8 +4075,8 @@ async function main () {
38904075
38914076 let selectedStep = null
38924077 try {
3893- selectedStep = ( await askQuestion ( 'Please select your step to run (0-7 ): ' ) ) . trim ( )
3894- const validSteps = new Set ( [ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' ] )
4078+ selectedStep = ( await askQuestion ( 'Please select your step to run (0-8 ): ' ) ) . trim ( )
4079+ const validSteps = new Set ( [ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ] )
38954080 if ( ! validSteps . has ( selectedStep ) ) {
38964081 console . log ( 'Unsupported step selected. Script is finished.' )
38974082 return
@@ -3905,7 +4090,7 @@ async function main () {
39054090 }
39064091
39074092 let dateFilter = null
3908- if ( [ '1' , '2' , '3' , '4' , '5' , '7' ] . includes ( selectedStep ) ) {
4093+ if ( [ '1' , '2' , '3' , '4' , '5' , '7' , '8' ] . includes ( selectedStep ) ) {
39094094 const dateFilterInput = ( await askQuestion ( 'Enter date filter (YYYY-MM-DD UTC; timestamp-less records will be skipped, or press Enter to skip): ' ) ) . trim ( )
39104095 if ( dateFilterInput ) {
39114096 const parsed = parseDateFilter ( dateFilterInput )
0 commit comments