feat(260723-lvg): add admin-gated POST /poll-now endpoint for tender radar
Manual "Jetzt abrufen" trigger delegates to
TenderIngestionService.pollDueSources() — the same fan-out tick the
scheduler cron runs. Gated to ADMIN/SUPER_ADMIN (T-lvg-01, DoS) and
declared before @Get(':id') per the established route-order convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import { TenderRssFeedDto } from './dto/tender-rss-feed.dto';
|
||||
import { TenderTriageDto } from './dto/tender-triage.dto';
|
||||
import { DENYLISTED_PORTALS, PORTAL_URLS } from './source-registry';
|
||||
import { TenderEmailConfigService } from './tender-email-config.service';
|
||||
import { TenderIngestionService } from './tender-ingestion.service';
|
||||
import { TenderNotificationPrefService } from './tender-notification-pref.service';
|
||||
import { TenderRssFeedSourceService } from './tender-rss-feed.service';
|
||||
import { TenderSavedSearchService } from './tender-saved-search.service';
|
||||
@@ -78,6 +79,15 @@ export class TendersController {
|
||||
private readonly tenderNotificationPref: TenderNotificationPrefService,
|
||||
private readonly tenderRssFeedSource: TenderRssFeedSourceService,
|
||||
private readonly tenderEmailConfig: TenderEmailConfigService,
|
||||
/**
|
||||
* Appended as the LAST constructor param (Quick 260723-lvg) — preserves
|
||||
* every existing `new TendersController(...7 args...)` call site in the
|
||||
* spec unchanged; those construct without this arg (undefined) and never
|
||||
* exercise `pollNow`. Declared optional (`?`) so those 7-arg call sites
|
||||
* still type-check — NestJS DI always resolves and injects it in
|
||||
* production (it is a registered provider in tenders.module.ts).
|
||||
*/
|
||||
private readonly tenderIngestionService?: TenderIngestionService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -189,6 +199,32 @@ export class TendersController {
|
||||
return config;
|
||||
}
|
||||
|
||||
// ─── Admin manual poll trigger (Quick 260723-lvg) ──────────────────────────
|
||||
|
||||
/**
|
||||
* POST /modules/tender-radar/poll-now — immediately run
|
||||
* `TenderIngestionService.pollDueSources()`, the same fan-out tick the
|
||||
* scheduler cron runs (INGEST-06). This fetches DUE sources now — it is
|
||||
* NOT a forced re-download: `'day'`-granularity sources (doe-opendata,
|
||||
* ai-netserver, cosinex-dtvp) still honor the `nextDayToFetch` day-cursor,
|
||||
* so a click after today's day was already ingested is a no-op for those
|
||||
* sources; `'tick'`-granularity sources (rss, email-alert) always fetch.
|
||||
* `pollDueSources()` never throws (catch-and-log per tick + per source),
|
||||
* so no try/catch is needed here.
|
||||
*
|
||||
* MUST be declared before `@Get(':id')` below — same route-order pitfall
|
||||
* as `source-config`/`coverage`/`rss-feeds`/... above (Pitfall 5).
|
||||
*
|
||||
* T-lvg-01 (DoS): gated to admins only — the platform-wide upstream fetch
|
||||
* is a rate lever, not a per-tenant module feature.
|
||||
*/
|
||||
@Post('poll-now')
|
||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||
async pollNow() {
|
||||
await this.tenderIngestionService!.pollDueSources();
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
// ─── RSS-Feeds admin CRUD (Roles-guarded, GLOBAL, D-08/D-14) ───────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user