@@ -119,6 +119,12 @@ type FullUser = {
119119 email : string ;
120120 gamesUpdate ?: number ;
121121 games : Game [ ] ;
122+ challenges : {
123+ issued : Set < string > ;
124+ received : Set < string > ;
125+ accepted : Set < string > ;
126+ standing : Set < string > ;
127+ }
122128 challenges_issued ?: Set < string > ;
123129 challenges_received ?: Set < string > ;
124130 challenges_accepted ?: Set < string > ;
@@ -682,6 +688,8 @@ module.exports.authQuery = async (event: { body: { query: any; pars: any; }; cog
682688 return await endATournament ( event . cognitoPoolClaims . sub , pars ) ;
683689 case "start_tournament" :
684690 return await startATournament ( event . cognitoPoolClaims . sub , pars ) ;
691+ case "migrate_challenges" :
692+ return await migrateChallenges ( event . cognitoPoolClaims . sub ) ;
685693 default :
686694 return {
687695 statusCode : 500 ,
@@ -8040,3 +8048,125 @@ const getAllUsers = async (): Promise<FullUser[]> => {
80408048 return result
80418049}
80428050
8051+ async function migrateChallenges ( userId : string ) {
8052+ // Make sure people aren't getting clever
8053+ try {
8054+ const user = await ddbDocClient . send (
8055+ new GetCommand ( {
8056+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8057+ Key : {
8058+ "pk" : "USER" ,
8059+ "sk" : userId
8060+ } ,
8061+ } ) ) ;
8062+ if ( user . Item === undefined || user . Item . admin !== true ) {
8063+ return {
8064+ statusCode : 200 ,
8065+ body : JSON . stringify ( { } ) ,
8066+ headers
8067+ } ;
8068+ }
8069+
8070+ console . log ( 'Starting challenge data migration...' ) ;
8071+
8072+ const users = await getAllUsers ( ) ;
8073+ const userChallengeData = users . filter ( user => user . challenges ) ;
8074+
8075+ console . log ( `Found ${ userChallengeData . length } users with challenge data out of ${ users . length } total users` ) ;
8076+
8077+ const migrationResults = {
8078+ totalUsers : users . length ,
8079+ usersWithChallenges : userChallengeData . length ,
8080+ migratedUsers : 0 ,
8081+ errors : [ ] as string [ ]
8082+ } ;
8083+
8084+ for ( const user of userChallengeData ) {
8085+ try {
8086+ const updates : any [ ] = [ ] ;
8087+
8088+ if ( user . challenges . issued && user . challenges . issued . size > 0 ) {
8089+ updates . push ( {
8090+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8091+ Key : { "pk" : "USER" , "sk" : user . id } ,
8092+ UpdateExpression : "ADD challenges_issued :ci" ,
8093+ ExpressionAttributeValues : { ":ci" : user . challenges . issued }
8094+ } ) ;
8095+ }
8096+
8097+ if ( user . challenges . received && user . challenges . received . size > 0 ) {
8098+ updates . push ( {
8099+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8100+ Key : { "pk" : "USER" , "sk" : user . id } ,
8101+ UpdateExpression : "ADD challenges_received :cr" ,
8102+ ExpressionAttributeValues : { ":cr" : user . challenges . received }
8103+ } ) ;
8104+ }
8105+
8106+ if ( user . challenges . standing && user . challenges . standing . size > 0 ) {
8107+ updates . push ( {
8108+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8109+ Key : { "pk" : "USER" , "sk" : user . id } ,
8110+ UpdateExpression : "ADD challenges_standing :cs" ,
8111+ ExpressionAttributeValues : { ":cs" : user . challenges . standing }
8112+ } ) ;
8113+ }
8114+
8115+ if ( user . challenges . accepted && user . challenges . accepted . size > 0 ) {
8116+ updates . push ( {
8117+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8118+ Key : { "pk" : "USER" , "sk" : user . id } ,
8119+ UpdateExpression : "ADD challenges_accepted :ca" ,
8120+ ExpressionAttributeValues : { ":ca" : user . challenges . accepted }
8121+ } ) ;
8122+ }
8123+
8124+ if ( updates . length > 0 ) {
8125+ // First, migrate to new top-level attributes
8126+ await Promise . all ( updates . map ( update => sendCommandWithRetry ( new UpdateCommand ( update ) ) ) ) ;
8127+
8128+ // Then delete the old nested attributes to prevent duplicates
8129+ const removeAttributes = [ ] ;
8130+ if ( user . challenges . issued && user . challenges . issued . size > 0 ) {
8131+ removeAttributes . push ( "challenges.issued" ) ;
8132+ }
8133+ if ( user . challenges . received && user . challenges . received . size > 0 ) {
8134+ removeAttributes . push ( "challenges.received" ) ;
8135+ }
8136+ if ( user . challenges . standing && user . challenges . standing . size > 0 ) {
8137+ removeAttributes . push ( "challenges.standing" ) ;
8138+ }
8139+ if ( user . challenges . accepted && user . challenges . accepted . size > 0 ) {
8140+ removeAttributes . push ( "challenges.accepted" ) ;
8141+ }
8142+
8143+ if ( removeAttributes . length > 0 ) {
8144+ await sendCommandWithRetry ( new UpdateCommand ( {
8145+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8146+ Key : { "pk" : "USER" , "sk" : user . id } ,
8147+ UpdateExpression : `REMOVE ${ removeAttributes . join ( ", " ) } ` ,
8148+ } ) ) ;
8149+ }
8150+
8151+ migrationResults . migratedUsers ++ ;
8152+ console . log ( `Migrated user ${ user . id } (${ migrationResults . migratedUsers } /${ userChallengeData . length } )` ) ;
8153+ }
8154+
8155+ await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
8156+
8157+ } catch ( error ) {
8158+ const errorMsg = `Failed to migrate user ${ user . id } : ${ error } ` ;
8159+ migrationResults . errors . push ( errorMsg ) ;
8160+ console . error ( errorMsg ) ;
8161+ }
8162+ }
8163+
8164+ console . log ( 'Challenge migration completed:' , migrationResults ) ;
8165+ return migrationResults ;
8166+
8167+ } catch ( error ) {
8168+ console . error ( 'Challenge migration failed:' , error ) ;
8169+ throw error ;
8170+ }
8171+ } ;
8172+
0 commit comments