feat(10-02): register TendersModule with marketplace self-seed + singleton poll config

- tenders.seed.ts seeds slug 'tender-radar' (isSystem: true, category 'procurement')
- tenders.module.ts self-seeds registry on boot (mirrors DkvModule pattern)
- upserts singleton doe-opendata TenderSourcePollConfig row (global, no forTenant())
- TendersModule added to app.module.ts imports
This commit is contained in:
2026-07-21 10:42:44 +02:00
parent 6cfda90960
commit e7b40946c8
3 changed files with 89 additions and 0 deletions
+2
View File
@@ -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()
+61
View File
@@ -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<void> {
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);
}
}
}
+26
View File
@@ -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<void> {
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,
});
}