import { Router, Request, Response } from 'express'; import { config } from '../../config'; import { claimSpecificAccountForManualQuota, getTurnitinAccountById, } from '../../db/accounts'; import { requireAdminSecret } from '../middleware/admin-secret'; import { getJobById } from '../../db/jobs'; import { runQuotaCheckForAccount } from '../../cron/quota-check'; import { logger } from '../../utils/logger'; const router = Router(); const MAX_MANUAL_QUOTA_CHECKS_PER_WORKER = 1; let activeManualQuotaChecks = 0; // Only /internal routes require the admin secret. Keep this scoped so unknown // public paths return 404 instead of noisy "admin secret mismatch" warnings. router.use('/internal', requireAdminSecret); /** * POST /internal/jobs/:id/run * Manually trigger a job run. Stub implementation — validates the job exists * and returns 202 Accepted. Actual worker invocation is handled elsewhere. */ router.post('/internal/jobs/:id/run', async (req: Request, res: Response): Promise => { const id = String(req.params.id); try { const job = await getJobById(id); if (!job) { res.status(404).json({ error: 'Job not found' }); return; } logger.info('Manual job run requested', { jobId: id }); // TODO: Integrate with worker dispatch res.status(202).json({ message: 'Job run accepted', jobId: id }); } catch (err) { logger.error('Internal job run error', { jobId: id, error: err instanceof Error ? err.message : String(err), }); res.status(500).json({ error: 'Internal server error' }); } }); /** * POST /internal/accounts/:id/quota-check * Manually trigger a quota check for a specific account. * The check is started in the background after the account is leased, so the * dashboard request is not held open for a full browser session. */ router.post( '/internal/accounts/:id/quota-check', async (req: Request, res: Response): Promise => { const id = String(req.params.id); let handedOffToBackground = false; try { logger.info('Manual quota check requested', { accountId: id }); if (activeManualQuotaChecks >= MAX_MANUAL_QUOTA_CHECKS_PER_WORKER) { res.status(429).json({ error: 'This worker is already running a manual quota check. Try another worker or wait briefly.', }); return; } activeManualQuotaChecks += 1; const existingAccount = await getTurnitinAccountById(id); if (!existingAccount) { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); res.status(404).json({ error: 'Turnitin account not found' }); return; } if (existingAccount.turnitin_status === 'disabled') { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); res.status(409).json({ error: 'This account is disabled and cannot be checked.' }); return; } if (existingAccount.turnitin_status === 'login_failed') { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); res.status(409).json({ error: 'This account is marked login_failed. Fix the credential before checking quota.', }); return; } if (existingAccount.turnitin_pool_key === 'legacy_carta' || existingAccount.turnitin_pool_key === 'modern_one') { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); res.status(409).json({ error: 'Manual quota check supports reusable modern Turnitin accounts only. Legacy and modern_one pools are finalized after submission.', }); return; } const leaseOwner = `${config.workerId}:manual-quota-check`; const claimedAccount = await claimSpecificAccountForManualQuota(id, leaseOwner); if (!claimedAccount) { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); res.status(409).json({ error: 'This account is currently leased by another worker or is not ready for checking.', }); return; } handedOffToBackground = true; void runQuotaCheckForAccount(claimedAccount, { source: 'manual', fallbackStatus: existingAccount.turnitin_status || 'available', }) .catch((err: unknown) => { logger.error('Manual quota check background task failed', { accountId: id, error: err instanceof Error ? err.message : String(err), }); }) .finally(() => { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); }); res.status(202).json({ message: 'Quota check started', accountId: id, workerId: config.workerId, }); } catch (err: unknown) { logger.error('Manual quota check request failed', { accountId: id, error: err instanceof Error ? err.message : String(err), }); if (!handedOffToBackground) { activeManualQuotaChecks = Math.max(0, activeManualQuotaChecks - 1); } res.status(500).json({ error: 'Failed to start quota check' }); } }, ); export default router;