feat(17-02): delete protection and 20-feed cap for personal RSS feeds
- remove(id, {userId, isAdmin}) replaces remove(id): single conditional
deleteMany (id AND (owned-by-caller OR admin-on-platform-feed)) — no
TOCTOU window, ownership check lives in the DB condition. Deletes
nothing -> NotFoundException (never Forbidden, no existence leak)
- createForUser rejects a caller's 21st personal feed with a clear
German message (T-17-10); platform-wide feeds are not counted
- DELETE /rss-feeds/:feedId moves from @Roles(ADMIN,SUPER_ADMIN) to
@UseModule('tender-radar') — ownership check does the gating now
- Tests use a Prisma double that actually evaluates the where condition
(not a double that always "succeeds") for both deleteMany and count
- Files modified: apps/api/src/tenders/tender-rss-feed.service.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tender-rss-feed.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts
This commit is contained in:
@@ -302,15 +302,27 @@ export class TendersController {
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /modules/tender-radar/rss-feeds/:feedId — remove a global RSS
|
||||
* feed. Uses `:feedId` (not `:id`) so this route can never be confused
|
||||
* with the Tender `:id` route below (Pitfall 5, same convention as
|
||||
* `saved-searches/:searchId`).
|
||||
* DELETE /modules/tender-radar/rss-feeds/:feedId — remove a feed.
|
||||
* Ownership is enforced entirely inside the service's single conditional
|
||||
* `deleteMany` (T-17-07): the caller may delete their own feed, or — if
|
||||
* ADMIN/SUPER_ADMIN — a platform-wide feed. Anything else (someone
|
||||
* else's personal feed, or a non-admin targeting a platform-wide feed)
|
||||
* surfaces as `NotFoundException`, never `ForbiddenException` — the
|
||||
* response never confirms whether a foreign id exists. Phase 17: replaced
|
||||
* `@Roles(ADMIN, SUPER_ADMIN)` with `@UseModule('tender-radar')` — every
|
||||
* module user may reach this route now, the ownership check does the
|
||||
* rest.
|
||||
*
|
||||
* Uses `:feedId` (not `:id`) so this route can never be confused with
|
||||
* the Tender `:id` route below (Pitfall 5, same convention as
|
||||
* `saved-searches/:searchId`). Route position UNCHANGED.
|
||||
*/
|
||||
@Delete('rss-feeds/:feedId')
|
||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||
async removeRssFeed(@Param('feedId') feedId: string) {
|
||||
return this.tenderRssFeedSource.remove(feedId);
|
||||
@UseModule('tender-radar')
|
||||
async removeRssFeed(@Param('feedId') feedId: string, @Req() req: Request) {
|
||||
const { userId, role } = this.extractTriageContext(req);
|
||||
const isAdmin = role === Role.ADMIN || role === Role.SUPER_ADMIN;
|
||||
return this.tenderRssFeedSource.remove(feedId, { userId, isAdmin });
|
||||
}
|
||||
|
||||
// ─── E-Mail-Alerts config (ModuleGuard-gated, PER-USER, Phase 17 D-01) ─────
|
||||
|
||||
Reference in New Issue
Block a user