--- phase: 03-module-system-domaincheck plan: 02 type: execute wave: 2 depends_on: ["03-01"] files_modified: - apps/api/src/domaincheck/domaincheck.module.ts - apps/api/src/domaincheck/domaincheck.service.ts - apps/api/src/domaincheck/domaincheck.controller.ts - apps/api/src/domaincheck/dto/check-domain.dto.ts - apps/api/src/domaincheck/domaincheck.seed.ts - apps/api/src/app.module.ts - apps/web/src/app/(portal)/modules/domaincheck/page.tsx - apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx - apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx - apps/web/src/app/(portal)/modules/domaincheck/actions.ts - apps/web/messages/de.json - apps/web/messages/en.json autonomous: true requirements: - DCHK-01 - DCHK-02 - DCHK-03 must_haves: truths: - "User can open the Domaincheck module and see an input field" - "User enters a domain name and sees TLD variant results" - "Results show green (available) or red (registered) status per TLD" artifacts: - path: "apps/api/src/domaincheck/domaincheck.service.ts" provides: "DNS-based domain availability checking" exports: ["DomaincheckService"] - path: "apps/api/src/domaincheck/domaincheck.controller.ts" provides: "POST /modules/domaincheck/check endpoint" exports: ["DomaincheckController"] - path: "apps/web/src/app/(portal)/modules/domaincheck/page.tsx" provides: "Domaincheck UI page within portal" key_links: - from: "apps/web/src/app/(portal)/modules/domaincheck/actions.ts" to: "/modules/domaincheck/check" via: "fetch POST to API" pattern: "fetch.*modules/domaincheck/check" - from: "apps/api/src/domaincheck/domaincheck.service.ts" to: "node:dns/promises" via: "dns.resolve for availability check" pattern: "dns\\.resolve" --- Deliver a working Domaincheck module end-to-end — user enters a domain name, backend checks TLD variants via DNS, frontend shows green/red results. This is the proof-of-concept module validating the entire module system. Purpose: Per D-01, D-02, D-03 — validates the module system with a real, interactive module. Users get immediate value. Output: DomaincheckModule (NestJS), domaincheck frontend page, module seed data. @/home/vicolab/.claude/gsd-core/workflows/execute-plan.md @/home/vicolab/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/03-module-system-domaincheck/03-CONTEXT.md @.planning/phases/03-module-system-domaincheck/03-RESEARCH.md @.planning/phases/03-module-system-domaincheck/03-01-SUMMARY.md @apps/api/src/module-registry/module-registry.service.ts @apps/api/src/module-registry/module.guard.ts @apps/web/src/app/(portal)/layout.tsx @apps/web/messages/de.json @apps/web/messages/en.json ## Phase Goal **As a** user, **I want to** enter a domain name and see which TLD variants are available or registered, **so that** I can quickly check domain availability without leaving the platform. Task 1: Domaincheck Backend — DNS Service + API Endpoint + Module Seed apps/api/src/domaincheck/domaincheck.module.ts, apps/api/src/domaincheck/domaincheck.service.ts, apps/api/src/domaincheck/domaincheck.controller.ts, apps/api/src/domaincheck/dto/check-domain.dto.ts, apps/api/src/domaincheck/domaincheck.seed.ts, apps/api/src/app.module.ts apps/api/src/module-registry/module-registry.service.ts, apps/api/src/module-registry/module.guard.ts, apps/api/src/app.module.ts 1. Create apps/api/src/domaincheck/dto/check-domain.dto.ts: - CheckDomainDto: domain (string, @IsString, @IsNotEmpty, @Matches regex for valid domain label — alphanumeric + hyphens, no dots) - Optional tlds (string array) — if omitted, use defaults 2. Create apps/api/src/domaincheck/domaincheck.service.ts: - DEFAULT_TLDS constant: ['de', 'com', 'net', 'org'] (per D-03 configurable, these are defaults) - Method checkDomain(domain: string, tlds?: string[]): Promise of array of { tld: string, fqdn: string, status: 'available' | 'registered' } - Implementation: use node:dns/promises — dns.resolve(fqdn) for each TLD variant - If resolves (any record) -> status = 'registered' - If throws with code ENOTFOUND or ENODATA -> status = 'available' - If throws with other error -> status = 'registered' (safe default) - Use Promise.allSettled for parallel lookups with 5-second timeout per lookup - Per D-02: system checks all TLD variants automatically from the base domain name 3. Create apps/api/src/domaincheck/domaincheck.controller.ts: - POST /modules/domaincheck/check - Protected by UseModule('domaincheck') decorator from module.guard.ts - Accepts CheckDomainDto body - Returns { domain: string, results: array of { tld, fqdn, status } } 4. Create apps/api/src/domaincheck/domaincheck.module.ts: - imports: PrismaModule, ModuleRegistryModule (for guard access) - providers: DomaincheckService - controllers: DomaincheckController 5. Create apps/api/src/domaincheck/domaincheck.seed.ts: - Export async function seedDomaincheckModule(moduleRegistryService: ModuleRegistryService) - Calls moduleRegistryService.seedModule with: slug='domaincheck', name='Domaincheck', version='1.0.0', category='domain-tools' (per D-09), description={ de: 'Domain-Verfuegbarkeit pruefen', en: 'Check domain availability' }, isSystem=true 6. Register DomaincheckModule in apps/api/src/app.module.ts imports. 7. Call the seed function from an OnModuleInit hook in DomaincheckModule (or add to an existing seeding mechanism) so the domaincheck module record exists in the database on startup. cd /home/vicolab/projects/tessera-ctl && pnpm --filter api build 2>&1 | tail -3 - API build succeeds with DomaincheckModule - POST /modules/domaincheck/check accepts { domain: "google" } and returns results array with tld, fqdn, status for each default TLD - DNS check correctly identifies google.com as registered - Module seed creates a "domaincheck" record in Module table on startup - UseModule('domaincheck') guard protects the endpoint — returns 403 if module not activated for tenant Domaincheck API endpoint works end-to-end: accepts domain, checks DNS for TLD variants, returns structured results Task 2: Domaincheck Frontend — Input Form + Results Display apps/web/src/app/(portal)/modules/domaincheck/page.tsx, apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx, apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx, apps/web/src/app/(portal)/modules/domaincheck/actions.ts, apps/web/messages/de.json, apps/web/messages/en.json apps/web/src/app/(portal)/page.tsx, apps/web/src/app/(portal)/layout.tsx, apps/web/messages/de.json, apps/web/messages/en.json 1. Add i18n keys to apps/web/messages/de.json under "domaincheck" namespace: - title: "Domaincheck" - description: "Pruefe ob eine Domain verfuegbar ist" - inputPlaceholder: "Domain eingeben (z.B. beispiel)" - checkButton: "Pruefen" - statusAvailable: "Verfuegbar" - statusRegistered: "Registriert" - noResults: "Gib einen Domain-Namen ein um die Verfuegbarkeit zu pruefen" - checking: "Pruefe..." - error: "Fehler bei der Pruefung" 2. Add same keys in English to apps/web/messages/en.json under "domaincheck" namespace: - title: "Domain Check" - description: "Check if a domain is available" - inputPlaceholder: "Enter domain (e.g. example)" - checkButton: "Check" - statusAvailable: "Available" - statusRegistered: "Registered" - noResults: "Enter a domain name to check availability" - checking: "Checking..." - error: "Error checking domain" 3. Create apps/web/src/app/(portal)/modules/domaincheck/actions.ts: - Server action or client-side fetch function checkDomainAction(domain: string): fetches POST to API_URL/modules/domaincheck/check with { domain }, passes auth cookie/token, returns typed results array 4. Create apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx: - Client component ("use client") - Input field (text) with placeholder from i18n - Submit button - Calls onSubmit prop with the domain value - Shows loading state while checking (per i18n "checking" key) 5. Create apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx: - Client component - Receives results array prop: { tld, fqdn, status }[] - Renders list/table of results - Per D-01: green badge/pill for "available", red badge/pill for "registered" - Per D-02: shows all TLD variants with their status - Empty state shows "noResults" message 6. Create apps/web/src/app/(portal)/modules/domaincheck/page.tsx: - Page component within (portal) route group (gets AppShell layout) - Uses DomainInput and ResultList components - Manages state with useState: domain input, results array, loading, error - On submit: calls checkDomainAction, updates results - Layout: title at top, input below, results list below input (per specifics in CONTEXT.md) - Use Tailwind classes consistent with existing portal pages - Use shadcn/ui Card component for the module panel (per D-05a) cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5 - Web app builds successfully with the new domaincheck route - /modules/domaincheck page renders within the portal AppShell - Input field accepts text, submit triggers API call - Results display with color-coded status badges (green=available, red=registered) - All visible strings use next-intl translations (no hardcoded text) - Works in both DE and EN locale User can navigate to /modules/domaincheck, enter a domain name, and see color-coded availability results for .de, .com, .net, .org TLD variants ## Trust Boundaries | Boundary | Description | |----------|-------------| | client -> POST /modules/domaincheck/check | User-supplied domain string | | API -> DNS | External DNS resolution (network) | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-03-05 | Tampering | domain input | mitigate | Regex validation in DTO — only alphanumeric + hyphens, max 63 chars, no dots allowed | | T-03-06 | Denial of Service | DNS lookups | mitigate | 5-second timeout per lookup, max 10 TLDs per request, Promise.allSettled prevents hanging | | T-03-07 | Information Disclosure | DNS results | accept | Domain availability is public information, no sensitive data exposed | | T-03-08 | Spoofing | module access | mitigate | UseModule('domaincheck') guard + JwtAuthGuard ensure only authenticated users with activated module can access | - pnpm --filter api build && pnpm --filter web build both succeed - POST /modules/domaincheck/check returns correct results for known domains - Frontend page renders and submits successfully - Module guard blocks access when module not activated for tenant - User can open /modules/domaincheck in browser (DCHK-01) - System checks TLD variants via DNS and returns status (DCHK-02) - Results show clear green/red status indicators (DCHK-03, D-01) - TLD list uses defaults ['de','com','net','org'] per D-03 ## Artifacts this phase produces | Symbol | Location | Type | |--------|----------|------| | DomaincheckService | apps/api/src/domaincheck/domaincheck.service.ts | class | | DomaincheckController | apps/api/src/domaincheck/domaincheck.controller.ts | class | | DomaincheckModule | apps/api/src/domaincheck/domaincheck.module.ts | NestJS module | | CheckDomainDto | apps/api/src/domaincheck/dto/check-domain.dto.ts | class | | seedDomaincheckModule | apps/api/src/domaincheck/domaincheck.seed.ts | function | | DomaincheckPage | apps/web/src/app/(portal)/modules/domaincheck/page.tsx | React component | | DomainInput | apps/web/src/app/(portal)/modules/domaincheck/components/DomainInput.tsx | React component | | ResultList | apps/web/src/app/(portal)/modules/domaincheck/components/ResultList.tsx | React component | | checkDomainAction | apps/web/src/app/(portal)/modules/domaincheck/actions.ts | function | Create `.planning/phases/03-module-system-domaincheck/03-02-SUMMARY.md` when done