File size: 5,161 Bytes
9179e11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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;