feat(11-06): saved-searches CRUD routes on TendersController (FILTER-06)
Adds GET/POST /saved-searches and PATCH/DELETE /saved-searches/:searchId,
registers TenderSavedSearchService as a module provider, and wires it into
the controller via extractTriageContext (userId/tenantId from the auth
context, never the body/query — T-11-14/V4 IDOR). Static saved-searches
routes are declared before @Get(':id') (Pitfall 5/T-11-16); mutation routes
use :searchId to avoid ambiguity with the Tender :id param.
Also fixes a Prisma InputJsonValue type mismatch in
TenderSavedSearchService (Rule 1 — caught by tsc --noEmit, same cast
convention as dashboard.service.ts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateSavedSearchDto, UpdateSavedSearchDto } from './dto/saved-search.dto';
|
||||
|
||||
@@ -45,7 +46,7 @@ export class TenderSavedSearchService {
|
||||
userId,
|
||||
tenantId,
|
||||
name: dto.name,
|
||||
filters: dto.filters,
|
||||
filters: dto.filters as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -76,7 +77,9 @@ export class TenderSavedSearchService {
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
if (dto.name !== undefined) data.name = dto.name;
|
||||
if (dto.filters !== undefined) data.filters = dto.filters;
|
||||
if (dto.filters !== undefined) {
|
||||
data.filters = dto.filters as unknown as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.prisma.tenderSavedSearch.update({
|
||||
|
||||
@@ -73,12 +73,37 @@ function makeFakeRequest(userId = 'u1', tenantId = 'tenant1') {
|
||||
return { user: { id: userId, tenantId }, tenantId } as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake TenderSavedSearchService for controller-level wiring tests (Plan
|
||||
* 11-06, Task 2). Default stubs return empty/echo results — individual
|
||||
* tests override via `.mockResolvedValueOnce`/reassigning the mock.
|
||||
*/
|
||||
function makeFakeSavedSearchService() {
|
||||
return {
|
||||
list: vi.fn(async (_userId: string) => [] as any[]),
|
||||
create: vi.fn(async (_userId: string, _tenantId: string, dto: any) => ({
|
||||
id: 'ss-1',
|
||||
...dto,
|
||||
})),
|
||||
update: vi.fn(async (_id: string, _userId: string, dto: any) => ({
|
||||
id: 'ss-1',
|
||||
...dto,
|
||||
})),
|
||||
remove: vi.fn(async (_id: string, _userId: string) => undefined),
|
||||
};
|
||||
}
|
||||
|
||||
describe('TendersController — global read (not tenant-scoped)', () => {
|
||||
it('GET / calls prisma.tender.findMany with a where clause that has no tenantId key', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({});
|
||||
|
||||
@@ -91,7 +116,12 @@ describe('TendersController — global read (not tenant-scoped)', () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
const result = await controller.getTender('t1');
|
||||
|
||||
@@ -104,7 +134,12 @@ describe('TendersController — global read (not tenant-scoped)', () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await expect(controller.getTender('missing')).rejects.toBeInstanceOf(NotFoundException);
|
||||
});
|
||||
@@ -115,7 +150,12 @@ describe('TendersController — admin source-config applies live to the schedule
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.saveSourceConfig({ isActive: true, pollIntervalMin: 30 });
|
||||
|
||||
@@ -128,7 +168,12 @@ describe('TendersController — admin source-config applies live to the schedule
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.saveSourceConfig({ isActive: false });
|
||||
|
||||
@@ -177,6 +222,99 @@ describe('TendersController — route declaration order (static route before :id
|
||||
expect(listTriageIdx).toBeLessThan(idIdx);
|
||||
expect(setTriageIdx).toBeLessThan(idIdx);
|
||||
});
|
||||
|
||||
it('declares listSavedSearches before getTender so GET /:id cannot shadow "saved-searches" (Plan 11-06, Pitfall 5/T-11-16)', () => {
|
||||
const methods = Object.getOwnPropertyNames(TendersController.prototype);
|
||||
const listSavedSearchesIdx = methods.indexOf('listSavedSearches');
|
||||
const idIdx = methods.indexOf('getTender');
|
||||
|
||||
expect(listSavedSearchesIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(idIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(listSavedSearchesIdx).toBeLessThan(idIdx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TendersController — saved-searches CRUD (FILTER-06, per-user, T-11-14/15/16)', () => {
|
||||
it('GET /saved-searches derives userId from req.user and delegates to tenderSavedSearch.list(userId) — never from a query param (V4 / IDOR)', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const savedSearchService = makeFakeSavedSearchService();
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
savedSearchService as any,
|
||||
);
|
||||
|
||||
await controller.listSavedSearches(makeFakeRequest('u-real'));
|
||||
|
||||
expect(savedSearchService.list).toHaveBeenCalledWith('u-real');
|
||||
});
|
||||
|
||||
it('POST /saved-searches delegates to tenderSavedSearch.create with userId/tenantId from the auth context, not the body', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const savedSearchService = makeFakeSavedSearchService();
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
savedSearchService as any,
|
||||
);
|
||||
|
||||
await controller.createSavedSearch(
|
||||
{ name: 'Bau NRW', filters: { q: 'Bau' } } as any,
|
||||
makeFakeRequest('u1', 'tenant1'),
|
||||
);
|
||||
|
||||
expect(savedSearchService.create).toHaveBeenCalledWith('u1', 'tenant1', {
|
||||
name: 'Bau NRW',
|
||||
filters: { q: 'Bau' },
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH /saved-searches/:searchId delegates to tenderSavedSearch.update(searchId, userId, dto) — userId from the auth context', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const savedSearchService = makeFakeSavedSearchService();
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
savedSearchService as any,
|
||||
);
|
||||
|
||||
await controller.updateSavedSearch(
|
||||
'ss-1',
|
||||
{ name: 'Neuer Name' } as any,
|
||||
makeFakeRequest('u1'),
|
||||
);
|
||||
|
||||
expect(savedSearchService.update).toHaveBeenCalledWith('ss-1', 'u1', {
|
||||
name: 'Neuer Name',
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /saved-searches/:searchId delegates to tenderSavedSearch.remove(searchId, userId) — userId from the auth context', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const savedSearchService = makeFakeSavedSearchService();
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
savedSearchService as any,
|
||||
);
|
||||
|
||||
const result = await controller.removeSavedSearch('ss-1', makeFakeRequest('u1'));
|
||||
|
||||
expect(savedSearchService.remove).toHaveBeenCalledWith('ss-1', 'u1');
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('TendersController — listTenders uses the query builder (sort whitelist + pagination bounds)', () => {
|
||||
@@ -184,7 +322,12 @@ describe('TendersController — listTenders uses the query builder (sort whiteli
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({ sort: 'deadline', q: 'Bau' } as any);
|
||||
|
||||
@@ -206,7 +349,12 @@ describe('TendersController — listTenders uses the query builder (sort whiteli
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({ sort: 'not-whitelisted' } as any);
|
||||
|
||||
@@ -218,7 +366,12 @@ describe('TendersController — listTenders uses the query builder (sort whiteli
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({ page: 3, limit: 10 } as any);
|
||||
|
||||
@@ -233,7 +386,12 @@ describe('TendersController — GET /coverage', () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
const result = await controller.getCoverage();
|
||||
|
||||
@@ -255,7 +413,12 @@ describe('TendersController — GET /triage (batch, per-user, T-11-10/11)', () =
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTriage('t1, t2 ,t3', makeFakeRequest('u1'));
|
||||
|
||||
@@ -266,7 +429,12 @@ describe('TendersController — GET /triage (batch, per-user, T-11-10/11)', () =
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTriage('t1', makeFakeRequest('u-real'));
|
||||
|
||||
@@ -277,7 +445,12 @@ describe('TendersController — GET /triage (batch, per-user, T-11-10/11)', () =
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
const manyIds = Array.from({ length: 300 }, (_, i) => `t${i}`).join(',');
|
||||
await controller.listTriage(manyIds, makeFakeRequest('u1'));
|
||||
@@ -292,7 +465,12 @@ describe('TendersController — PUT /triage (upsert, per-user, T-11-10)', () =>
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.setTriage(
|
||||
{ tenderId: 't1', isRead: true } as any,
|
||||
@@ -312,7 +490,12 @@ describe('TendersController — listTenders favOnly wiring (UI-04, T-11-10/11)',
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
triageService.favoriteIds.mockResolvedValueOnce(['t1']);
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({ favOnly: true } as any, makeFakeRequest('u1'));
|
||||
|
||||
@@ -328,7 +511,12 @@ describe('TendersController — listTenders favOnly wiring (UI-04, T-11-10/11)',
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
triageService.favoriteIds.mockResolvedValueOnce([]);
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({ favOnly: true } as any, makeFakeRequest('u1'));
|
||||
|
||||
@@ -342,7 +530,12 @@ describe('TendersController — listTenders favOnly wiring (UI-04, T-11-10/11)',
|
||||
const prisma = makeFakePrisma();
|
||||
const scheduler = { setInterval: vi.fn(), stopJob: vi.fn() } as any;
|
||||
const triageService = makeFakeTriageService();
|
||||
const controller = new TendersController(prisma as any, scheduler, triageService as any);
|
||||
const controller = new TendersController(
|
||||
prisma as any,
|
||||
scheduler,
|
||||
triageService as any,
|
||||
makeFakeSavedSearchService() as any,
|
||||
);
|
||||
|
||||
await controller.listTenders({} as any, makeFakeRequest('u1'));
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
ForbiddenException,
|
||||
Get,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Req,
|
||||
@@ -14,9 +17,11 @@ import { Request } from 'express';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { UseModule } from '../module-registry/module.guard';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateSavedSearchDto, UpdateSavedSearchDto } from './dto/saved-search.dto';
|
||||
import { SourceConfigDto } from './dto/source-config.dto';
|
||||
import { TenderQueryDto } from './dto/tender-query.dto';
|
||||
import { TenderTriageDto } from './dto/tender-triage.dto';
|
||||
import { TenderSavedSearchService } from './tender-saved-search.service';
|
||||
import { TenderSchedulerService } from './tender-scheduler.service';
|
||||
import { TenderTriageService } from './tender-triage.service';
|
||||
import { buildOrderBy, buildTenderWhere } from './tender-query.builder';
|
||||
@@ -52,6 +57,7 @@ export class TendersController {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly tenderScheduler: TenderSchedulerService,
|
||||
private readonly tenderTriage: TenderTriageService,
|
||||
private readonly tenderSavedSearch: TenderSavedSearchService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -227,6 +233,74 @@ export class TendersController {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Saved Searches (per-user, FILTER-06, D-08/D-11) ───────────────────────
|
||||
|
||||
/**
|
||||
* GET /modules/tender-radar/saved-searches — list the current user's
|
||||
* saved search profiles (FILTER-06). Scoped strictly by userId
|
||||
* (T-11-14 / V4 — IDOR), derived from the auth context, never from a
|
||||
* query param.
|
||||
*
|
||||
* MUST be declared before `@Get(':id')` below — same route-order pitfall
|
||||
* as `source-config`/`coverage`/`triage` above (Pitfall 5, T-11-16).
|
||||
*/
|
||||
@Get('saved-searches')
|
||||
@UseModule('tender-radar')
|
||||
async listSavedSearches(@Req() req: Request) {
|
||||
const { userId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.list(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /modules/tender-radar/saved-searches — create a new saved search
|
||||
* profile. userId/tenantId come exclusively from the auth context
|
||||
* (T-11-14 / V4 — IDOR); `dto` carries only name/filters, never a
|
||||
* userId field.
|
||||
*/
|
||||
@Post('saved-searches')
|
||||
@UseModule('tender-radar')
|
||||
async createSavedSearch(
|
||||
@Body() dto: CreateSavedSearchDto,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const { userId, tenantId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.create(userId, tenantId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /modules/tender-radar/saved-searches/:searchId — rename and/or
|
||||
* update the filters of an existing saved search. Ownership is verified
|
||||
* in TenderSavedSearchService.update() (T-11-14). Uses `:searchId`
|
||||
* (not `:id`) so this route can never be confused with the Tender
|
||||
* `:id` param below (Pitfall 5).
|
||||
*/
|
||||
@Patch('saved-searches/:searchId')
|
||||
@UseModule('tender-radar')
|
||||
async updateSavedSearch(
|
||||
@Param('searchId') searchId: string,
|
||||
@Body() dto: UpdateSavedSearchDto,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const { userId } = this.extractTriageContext(req);
|
||||
return this.tenderSavedSearch.update(searchId, userId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /modules/tender-radar/saved-searches/:searchId — delete a saved
|
||||
* search. Ownership verified in TenderSavedSearchService.remove()
|
||||
* (T-11-14).
|
||||
*/
|
||||
@Delete('saved-searches/:searchId')
|
||||
@UseModule('tender-radar')
|
||||
async removeSavedSearch(
|
||||
@Param('searchId') searchId: string,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const { userId } = this.extractTriageContext(req);
|
||||
await this.tenderSavedSearch.remove(searchId, userId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /modules/tender-radar/:id — single tender detail.
|
||||
* Gated by @UseModule('tender-radar'); NOT scoped by the tenant's id
|
||||
|
||||
@@ -6,6 +6,7 @@ import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter';
|
||||
import { seedTendersModule } from './tenders.seed';
|
||||
import { TenderIngestionService } from './tender-ingestion.service';
|
||||
import { TenderNormalizerService } from './tender-normalizer.service';
|
||||
import { TenderSavedSearchService } from './tender-saved-search.service';
|
||||
import { TenderSchedulerService } from './tender-scheduler.service';
|
||||
import { TenderTriageService } from './tender-triage.service';
|
||||
import { TendersController } from './tenders.controller';
|
||||
@@ -26,6 +27,10 @@ import { TendersController } from './tenders.controller';
|
||||
*
|
||||
* PrismaModule is global (no explicit import needed).
|
||||
*
|
||||
* Plan 06 adds TenderSavedSearchService (per-user Suchprofile, FILTER-06)
|
||||
* as a further provider — same userId-scoping convention as
|
||||
* TenderTriageService, no forTenant()/RLS (Pitfall 4).
|
||||
*
|
||||
* Seeds itself into the module registry on application startup via
|
||||
* OnModuleInit lifecycle hook — same pattern as DkvModule.
|
||||
*/
|
||||
@@ -38,6 +43,7 @@ import { TendersController } from './tenders.controller';
|
||||
TenderIngestionService,
|
||||
TenderSchedulerService,
|
||||
TenderTriageService,
|
||||
TenderSavedSearchService,
|
||||
],
|
||||
})
|
||||
export class TendersModule implements OnModuleInit {
|
||||
|
||||
Reference in New Issue
Block a user