feat(14-04): denylisted-portals read endpoint sourced from DENYLISTED_PORTALS

- Add PORTAL_URLS map (vergabe24, aumass) in source-registry.ts, keyed off
  the existing DENYLISTED_PORTALS constant so the portal set is never
  re-declared
- Add GET /modules/tender-radar/denylisted-portals, declared before
  @Get(':id') (route-order pitfall), mapping over DENYLISTED_PORTALS
- Extend tenders.controller.spec.ts: response shape + route-order guard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 13:58:25 +02:00
parent c140d1186a
commit 28c6c7fee1
3 changed files with 72 additions and 0 deletions
@@ -24,6 +24,7 @@ import { TenderEmailConfigDto } from './dto/tender-email-config.dto';
import { TenderQueryDto } from './dto/tender-query.dto';
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 { TenderNotificationPrefService } from './tender-notification-pref.service';
import { TenderRssFeedSourceService } from './tender-rss-feed.service';
@@ -295,6 +296,29 @@ export class TendersController {
};
}
/**
* GET /modules/tender-radar/denylisted-portals — the AGB-prohibited
* portals (vergabe24, aumass) with their canonical direct-link URLs
* (UI-06/D-12). Maps over `DENYLISTED_PORTALS` (source-registry.ts) —
* the portal SET is never re-declared here, so a future denylist entry
* (with a URL added to `PORTAL_URLS`) flows through automatically.
*
* MUST be declared before `@Get(':id')` below — same route-order
* pitfall as `source-config`/`coverage`/... above (Pitfall 5). Read-
* surface, gated by @UseModule (not admin-only) — same stance as
* `getCoverage`.
*/
@Get('denylisted-portals')
@UseModule('tender-radar')
async getDenylistedPortals() {
return {
portals: DENYLISTED_PORTALS.map((portal) => ({
portal,
url: PORTAL_URLS[portal],
})),
};
}
/**
* GET /modules/tender-radar/triage?ids=<csv> — batch-fetch the current
* user's triage state (gelesen/ungelesen, Favorit) for the given