---
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 |