feat(17-02): RSS feeds get an owner — platform-wide vs personal (D-02)
- TenderRssFeedSource.userId/tenantId (nullable): null = platform-wide
(admin-managed, includes the existing service.bund.de default),
set = personal feed owned by exactly one user
- Migration replaces url @unique with @@unique([userId, url]) — two
users can now follow the same address independently; existing rows
keep an empty owner (platform-wide, unchanged behavior)
- Service: listForUser/createForUser/createPlatform replace list/create
- Controller: GET/POST /rss-feeds move from @Roles(ADMIN,SUPER_ADMIN) to
@UseModule('tender-radar'); POST with scope:'platform' still requires
ADMIN/SUPER_ADMIN, checked inline (T-17-08)
- tenders.module.ts seed switched from upsert-on-url to find-then-create
(Rule 3, pulled forward from Task 3): the new compound unique index
requires a non-null userId in Prisma's generated type, so a
platform-wide row can no longer be addressed via upsert
- Files modified: apps/api/prisma/schema.prisma, apps/api/prisma/migrations/20260812110000_tender_rss_feed_owner/migration.sql, apps/api/src/tenders/tender-rss-feed.service.ts, apps/api/src/tenders/dto/tender-rss-feed.dto.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tenders.module.ts, apps/api/src/tenders/tender-rss-feed.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsIn,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
@@ -8,13 +9,14 @@ import {
|
||||
} from 'class-validator';
|
||||
|
||||
/**
|
||||
* DTO for admin-managed RSS feed sources (`TenderRssFeedSource`, D-14/D-08).
|
||||
* DTO for RSS feed sources (`TenderRssFeedSource`, D-14/D-08; ownership
|
||||
* split personal/platform-wide since Phase 17, Plan 02, D-02).
|
||||
*
|
||||
* `@IsUrl` here is only a COARSE well-formedness check (http/https,
|
||||
* protocol required). The SUBSTANTIVE SSRF/denylist guard — rejecting
|
||||
* DENYLISTED_PORTALS hostnames and private/loopback hosts — is enforced in
|
||||
* `TenderRssFeedSourceService.assertUrlAllowed()`, NOT here (RESEARCH.md
|
||||
* Pitfall 3: an admin-supplied RSS feed URL is runtime data, added long
|
||||
* Pitfall 3: a user-supplied RSS feed URL is runtime data, added long
|
||||
* after `SourceRegistry.register()`'s DI-boot-time denylist check runs —
|
||||
* this DTO alone provides zero protection against a feed URL pointing at
|
||||
* vergabe24/aumass or an internal host). `require_tld: false` deliberately
|
||||
@@ -38,4 +40,15 @@ export class TenderRssFeedDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
/**
|
||||
* A WISH only, never trusted as authorization by itself (D-02): 'platform'
|
||||
* additionally requires the caller to hold ADMIN/SUPER_ADMIN, enforced
|
||||
* server-side in `TendersController.createRssFeed` — never here or in the
|
||||
* service. Default 'personal' so an ordinary module user's POST creates a
|
||||
* feed they own without needing to know this field exists.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsIn(['personal', 'platform'])
|
||||
scope?: 'personal' | 'platform';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user