feat(scheduler): in-process matching scheduler with audit + admin endpoints
- Add in-process scheduler service triggered by ENABLE_SCHEDULER - Record runs in new matching_runs table; throttle per-event and log stats - Add admin endpoints: POST /api/admin/events/:slug/run-now and GET /api/admin/events/:slug/matching-runs - Wire scheduler start/stop in server and add ENV flags + compose defaults - Prisma schema: add MatchingRun model and relation - Update env examples for scheduler configuration
This commit is contained in:
114
backend/src/routes/admin.js
Normal file
114
backend/src/routes/admin.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const express = require('express');
|
||||
const { prisma } = require('../utils/db');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
const matchingService = require('../services/matching');
|
||||
const { SUGGESTION_STATUS } = require('../constants');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// POST /api/admin/events/:slug/run-now - Trigger matching immediately for an event
|
||||
router.post('/events/:slug/run-now', authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
|
||||
const event = await prisma.event.findUnique({
|
||||
where: { slug },
|
||||
select: { id: true, slug: true },
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
return res.status(404).json({ success: false, error: 'Event not found' });
|
||||
}
|
||||
|
||||
const startedAt = new Date();
|
||||
const runRow = await prisma.matchingRun.create({
|
||||
data: {
|
||||
eventId: event.id,
|
||||
trigger: 'manual',
|
||||
status: 'running',
|
||||
startedAt,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const suggestions = await matchingService.runMatching(event.id);
|
||||
await matchingService.saveMatchingResults(event.id, suggestions);
|
||||
|
||||
const notFoundCount = suggestions.filter(s => s.status === SUGGESTION_STATUS.NOT_FOUND).length;
|
||||
const matchedCount = suggestions.filter(s => s.status === SUGGESTION_STATUS.PENDING).length;
|
||||
|
||||
await prisma.matchingRun.update({
|
||||
where: { id: runRow.id },
|
||||
data: {
|
||||
status: 'success',
|
||||
endedAt: new Date(),
|
||||
matchedCount,
|
||||
notFoundCount,
|
||||
},
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
eventSlug: event.slug,
|
||||
startedAt,
|
||||
endedAt: new Date(),
|
||||
matched: matchedCount,
|
||||
notFound: notFoundCount,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
await prisma.matchingRun.update({
|
||||
where: { id: runRow.id },
|
||||
data: {
|
||||
status: 'error',
|
||||
endedAt: new Date(),
|
||||
error: String(err?.message || err),
|
||||
},
|
||||
});
|
||||
return res.status(500).json({ success: false, error: 'Matching failed', details: String(err?.message || err) });
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/admin/events/:slug/matching-runs?limit=20 - List recent runs
|
||||
router.get('/events/:slug/matching-runs', authenticate, async (req, res, next) => {
|
||||
try {
|
||||
const { slug } = req.params;
|
||||
const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
|
||||
|
||||
const event = await prisma.event.findUnique({
|
||||
where: { slug },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
return res.status(404).json({ success: false, error: 'Event not found' });
|
||||
}
|
||||
|
||||
const runs = await prisma.matchingRun.findMany({
|
||||
where: { eventId: event.id },
|
||||
orderBy: { startedAt: 'desc' },
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
trigger: true,
|
||||
status: true,
|
||||
startedAt: true,
|
||||
endedAt: true,
|
||||
matchedCount: true,
|
||||
notFoundCount: true,
|
||||
error: true,
|
||||
},
|
||||
});
|
||||
|
||||
res.json({ success: true, count: runs.length, data: runs });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user