110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
import { fetchGradesFromScraper } from './scraper.service';
|
|
import { syncGradesToDatabase, getAllUsers } from './grades.service';
|
|
|
|
interface SyncResult {
|
|
username: string;
|
|
success: boolean;
|
|
error?: string;
|
|
classesProcessed?: number;
|
|
assignmentsAdded?: number;
|
|
assignmentsUpdated?: number;
|
|
}
|
|
|
|
export async function syncUserGrades(
|
|
username: string,
|
|
password: string
|
|
): Promise<SyncResult> {
|
|
try {
|
|
console.log(`[Sync] Starting sync for user: ${username}`);
|
|
|
|
// Step 1: Fetch from scraper
|
|
const gradesData = await fetchGradesFromScraper(username, password);
|
|
|
|
// Step 2: Save to database
|
|
const result = await syncGradesToDatabase(username, password, gradesData);
|
|
|
|
console.log(`[Sync] ✓ Completed for ${username}: ${result.classesProcessed} classes, ${result.assignmentsAdded} new, ${result.assignmentsUpdated} updated`);
|
|
|
|
return {
|
|
username,
|
|
success: true,
|
|
classesProcessed: result.classesProcessed,
|
|
assignmentsAdded: result.assignmentsAdded,
|
|
assignmentsUpdated: result.assignmentsUpdated,
|
|
};
|
|
} catch (error) {
|
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
console.error(`[Sync] ✗ Failed for ${username}: ${errorMsg}`);
|
|
|
|
return {
|
|
username,
|
|
success: false,
|
|
error: errorMsg,
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function syncMultipleUsers(): Promise<{
|
|
total: number;
|
|
successful: number;
|
|
failed: number;
|
|
results: SyncResult[];
|
|
duration: number;
|
|
}> {
|
|
const startTime = Date.now();
|
|
const maxConcurrent = parseInt(process.env.MAX_CONCURRENT_SYNCS || '3');
|
|
|
|
console.log(`\n[Sync Job] Starting batch sync (max ${maxConcurrent} concurrent)`);
|
|
|
|
// Get all users with their credentials
|
|
const users = await getAllUsers();
|
|
|
|
if (users.length === 0) {
|
|
console.log('[Sync Job] No users to sync');
|
|
return {
|
|
total: 0,
|
|
successful: 0,
|
|
failed: 0,
|
|
results: [],
|
|
duration: 0,
|
|
};
|
|
}
|
|
|
|
// We need to get passwords, so fetch full user records
|
|
const { prisma } = await import('../db');
|
|
const usersWithPasswords = await prisma.user.findMany({
|
|
select: {
|
|
username: true,
|
|
password: true,
|
|
},
|
|
});
|
|
|
|
const results: SyncResult[] = [];
|
|
|
|
// Process in batches
|
|
for (let i = 0; i < usersWithPasswords.length; i += maxConcurrent) {
|
|
const batch = usersWithPasswords.slice(i, i + maxConcurrent);
|
|
console.log(`[Sync Job] Processing batch ${Math.floor(i / maxConcurrent) + 1}: ${batch.map(u => u.username).join(', ')}`);
|
|
|
|
const batchPromises = batch.map(user =>
|
|
syncUserGrades(user.username, user.password)
|
|
);
|
|
|
|
const batchResults = await Promise.all(batchPromises);
|
|
results.push(...batchResults);
|
|
}
|
|
|
|
const successful = results.filter(r => r.success).length;
|
|
const failed = results.filter(r => !r.success).length;
|
|
const duration = Date.now() - startTime;
|
|
|
|
console.log(`[Sync Job] Completed: ${successful} successful, ${failed} failed (${(duration / 1000).toFixed(1)}s)\n`);
|
|
|
|
return {
|
|
total: users.length,
|
|
successful,
|
|
failed,
|
|
results,
|
|
duration,
|
|
};
|
|
} |