diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 28dd07e..9da6863 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -20,6 +20,7 @@ import { PrismaModule } from './prisma/prisma.module'; import { SettingsModule } from './settings/settings.module'; import { TenantGuard } from './tenant/tenant.guard'; import { TenantModule } from './tenant/tenant.module'; +import { TendersModule } from './tenders/tenders.module'; import { UserModule } from './user/user.module'; @Module({ @@ -41,6 +42,7 @@ import { UserModule } from './user/user.module'; SettingsModule, DkvModule, FavoritesModule, + TendersModule, ], providers: [ // Global JWT guard: all routes require auth unless @Public() diff --git a/apps/api/src/tenders/tenders.module.ts b/apps/api/src/tenders/tenders.module.ts new file mode 100644 index 0000000..8f3491f --- /dev/null +++ b/apps/api/src/tenders/tenders.module.ts @@ -0,0 +1,61 @@ +import { Logger, Module, OnModuleInit } from '@nestjs/common'; +import { ModuleRegistryModule } from '../module-registry/module-registry.module'; +import { ModuleRegistryService } from '../module-registry/module-registry.service'; +import { PrismaService } from '../prisma/prisma.service'; +import { seedTendersModule } from './tenders.seed'; + +/** + * NestJS module for the Ausschreibungs-Radar feature. + * + * Wave 2 (this plan) only registers the module in the marketplace and + * seeds the singleton DÖE poll config so the shared poll is + * admin-drivable. Ingestion/adapter/scheduler/controller wiring is + * added in Plans 03-05. + * + * PrismaModule is global (no explicit import needed). + * + * Seeds itself into the module registry on application startup via + * OnModuleInit lifecycle hook — same pattern as DkvModule. + */ +@Module({ + imports: [ModuleRegistryModule], + controllers: [], + providers: [], +}) +export class TendersModule implements OnModuleInit { + private readonly logger = new Logger(TendersModule.name); + + constructor( + private readonly moduleRegistryService: ModuleRegistryService, + private readonly prisma: PrismaService, + ) {} + + async onModuleInit(): Promise { + try { + await seedTendersModule(this.moduleRegistryService); + this.logger.log('Ausschreibungs-Radar module seeded in registry'); + } catch (error) { + this.logger.error('Failed to seed Ausschreibungs-Radar module', error); + } + + try { + // Singleton poll config — global, RLS-exempt (D-03). Do NOT use + // forTenant() here: this row is shared platform-wide, not per-tenant. + // isActive defaults true so the platform-global DÖE poll (D-04 + // hourly) runs regardless of tenant activation; the day-cursor + // gate (Plan 04) makes repeated ticks idempotent. + await this.prisma.tenderSourcePollConfig.upsert({ + where: { sourceType: 'doe-opendata' }, + update: {}, + create: { + sourceType: 'doe-opendata', + pollIntervalMin: 60, + isActive: true, + }, + }); + this.logger.log('doe-opendata poll config seeded'); + } catch (error) { + this.logger.error('Failed to seed doe-opendata poll config', error); + } + } +} diff --git a/apps/api/src/tenders/tenders.seed.ts b/apps/api/src/tenders/tenders.seed.ts new file mode 100644 index 0000000..55e305f --- /dev/null +++ b/apps/api/src/tenders/tenders.seed.ts @@ -0,0 +1,26 @@ +import { ModuleRegistryService } from '../module-registry/module-registry.service'; + +/** + * Seeds the tender-radar module into the module registry. + * + * Called during TendersModule initialization to ensure the + * "tender-radar" module record exists in the database (CONFIG-01). + * + * isSystem: true registers the module in the registry but does NOT + * auto-activate it per tenant. Admin must activate via Marketplace UI. + */ +export async function seedTendersModule( + moduleRegistryService: ModuleRegistryService, +): Promise { + await moduleRegistryService.seedModule({ + slug: 'tender-radar', + name: 'Ausschreibungs-Radar', + version: '1.0.0', + category: 'procurement', + description: { + de: 'Deutsche Ausschreibungen automatisch erfassen und durchsuchen', + en: 'Automatically track and search German public tenders', + }, + isSystem: true, + }); +}