@@ -43,6 +43,64 @@ const cleanupDuplicateProjectUsers = async function(): Promise<void> {
4343 }
4444} ;
4545
46+ const cleanupNonExistentCursusUsers = async function ( api : Fast42 ) : Promise < void > {
47+ // This function makes sure to clean up cursus_users that no longer exist in the API.
48+ // This can happen when an applicant unsubscribes from a kickoff or a piscine through Apply.
49+ // Fetch cursus_users from the database, limited with a begin_at within the past year or the future.
50+ // If cleanup further is needed, manually delete all cursus_users from the database and reseed.
51+ const cursusUsers = await prisma . cursusUser . findMany ( {
52+ select : {
53+ id : true ,
54+ user_id : true ,
55+ cursus_id : true ,
56+ } ,
57+ where : {
58+ begin_at : {
59+ gte : new Date ( Date . now ( ) - 365 * 24 * 60 * 60 * 1000 ) , // within the past year
60+ } ,
61+ } ,
62+ } ) ;
63+
64+ // Fetch these cursus_users again from the API, by AMOUNT_PER_PAGE per page using filters
65+ const AMOUNT_PER_PAGE = 30 ;
66+ const pagesToFetch = Math . ceil ( cursusUsers . length / AMOUNT_PER_PAGE ) ;
67+ console . log ( `Found ${ cursusUsers . length } cursus_users in the database. Cleaning up non-existent ones (will take ${ pagesToFetch } API calls)...` ) ;
68+ for ( let i = 0 ; i < cursusUsers . length ; i += AMOUNT_PER_PAGE ) {
69+ const page = cursusUsers . slice ( i , i + AMOUNT_PER_PAGE ) ;
70+ const ids = page . map ( ( cursusUser ) => cursusUser . id ) ;
71+ try {
72+ console . log ( `Cursus_user cleanup: Fetching page ${ i / AMOUNT_PER_PAGE + 1 } of ${ pagesToFetch } ...` ) ;
73+ const cursusUsersData = await fetchSingle42ApiPage ( api , '/cursus_users' , {
74+ 'filter[id]' : ids . join ( ',' ) ,
75+ 'page[size]' : AMOUNT_PER_PAGE . toString ( ) ,
76+ 'page[number]' : '1' ,
77+ } ) ;
78+ if ( ! cursusUsersData || cursusUsersData . length === 0 ) {
79+ console . warn ( `No cursus_users found at all for ids ${ ids . join ( ', ' ) } , this is possibly a bug, skipping deletion!` ) ;
80+ continue ;
81+ }
82+ const fetchedIds = cursusUsersData . map ( ( cursusUser : { id : number } ) => cursusUser . id ) ;
83+ // Find the ids that are not in the fetched data
84+ const nonExistentIds = ids . filter ( ( id ) => ! fetchedIds . includes ( id ) ) ;
85+ if ( nonExistentIds . length > 0 ) {
86+ console . log ( `Removing ${ nonExistentIds . length } non-existent local cursus_users with ids: ${ nonExistentIds . join ( ', ' ) } ` ) ;
87+ await prisma . cursusUser . deleteMany ( {
88+ where : {
89+ id : {
90+ in : nonExistentIds ,
91+ } ,
92+ } ,
93+ } ) ;
94+ }
95+ }
96+ catch ( err ) {
97+ console . error ( `Error fetching cursus_users for ids ${ ids . join ( ', ' ) } : ${ err } ` ) ;
98+ continue ;
99+ }
100+ }
101+ console . log ( 'Finished cleaning up non-existent cursus_users.' ) ;
102+ } ;
103+
46104const anonymizeUsers = async function ( api : Fast42 ) : Promise < void > {
47105 // Fetch all users where the anonymize_date is in the past, not null and the login does not yet start with 3b3
48106 const users = await prisma . user . findMany ( {
@@ -92,5 +150,6 @@ const anonymizeUsers = async function(api: Fast42): Promise<void> {
92150export const cleanupDB = async function ( api : Fast42 ) : Promise < void > {
93151 console . info ( 'Cleaning up the database...' ) ;
94152 await cleanupDuplicateProjectUsers ( ) ;
153+ await cleanupNonExistentCursusUsers ( api ) ;
95154 await anonymizeUsers ( api ) ;
96155} ;
0 commit comments