Spaces:
Sleeping
Sleeping
| 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<void> => { | |
| 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<void> => { | |
| 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; | |