89f559ce62
4 plans across 3 waves: SDK + registry (W1), Domaincheck module + lazy loading (W2), visual verification (W3). Covers MOD-01..04, DCHK-01..03. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
274 lines
13 KiB
Markdown
274 lines
13 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/vicolab/.claude/gsd-core/workflows/execute-plan.md
|
|
@/home/vicolab/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.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
|
|
</context>
|
|
|
|
## 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.
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Domaincheck Backend — DNS Service + API Endpoint + Module Seed</name>
|
|
<files>
|
|
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
|
|
</files>
|
|
<read_first>
|
|
apps/api/src/module-registry/module-registry.service.ts,
|
|
apps/api/src/module-registry/module.guard.ts,
|
|
apps/api/src/app.module.ts
|
|
</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter api build 2>&1 | tail -3</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>Domaincheck API endpoint works end-to-end: accepts domain, checks DNS for TLD variants, returns structured results</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Domaincheck Frontend — Input Form + Results Display</name>
|
|
<files>
|
|
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
|
|
</files>
|
|
<read_first>
|
|
apps/web/src/app/(portal)/page.tsx,
|
|
apps/web/src/app/(portal)/layout.tsx,
|
|
apps/web/messages/de.json,
|
|
apps/web/messages/en.json
|
|
</read_first>
|
|
<action>
|
|
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)
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter web build 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>User can navigate to /modules/domaincheck, enter a domain name, and see color-coded availability results for .de, .com, .net, .org TLD variants</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## 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 |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- 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
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 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
|
|
</success_criteria>
|
|
|
|
## 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 |
|
|
|
|
<output>
|
|
Create `.planning/phases/03-module-system-domaincheck/03-02-SUMMARY.md` when done
|
|
</output>
|