# Phase 3: Module System & Domaincheck - Research **Researched:** 2026-06-19 **Status:** Complete ## Module System Architecture ### NestJS Dynamic Modules Pattern NestJS provides `DynamicModule` for runtime module configuration. For Tessera's use case: 1. **Module Registry Service** — A core NestJS service that manages module metadata in PostgreSQL 2. **Module Loader** — Discovers and registers module routes at startup, checks activation per request 3. **Module Guard** — NestJS guard that checks if a module is activated for the requesting tenant **Pattern:** ``` ModuleRegistryModule (core) ├── ModuleRegistryService — CRUD for module metadata ├── ModuleActivationService — per-tenant activation/deactivation ├── ModuleGuard — checks activation before allowing access └── ModuleController — admin endpoints for module management ``` ### Frontend Lazy Loading with Next.js App Router Next.js App Router supports: - `next/dynamic` with `{ ssr: false }` for client-only lazy components - Route-based code splitting via file-system routing (automatic) - Dynamic route segments: `app/(portal)/modules/[moduleId]/page.tsx` **Recommended approach for Tessera:** - Each module registers a frontend component path in the registry - Category pages load module cards/panels via dynamic imports - Expanded view uses a dynamic route: `/modules/[moduleId]` - Inactive modules are never imported (no bundle impact) ### Module SDK Design (@tessera/module-sdk) The SDK package defines the contract between the platform and modules: ```typescript interface TesseraModule { id: string; name: string; version: string; category: string; description: Record; // i18n: { de: '...', en: '...' } icon?: string; // Backend routes?: ModuleRoute[]; // Frontend component: () => Promise; cardComponent?: () => Promise; // compact card view } interface ModuleRoute { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; path: string; handler: string; // reference to controller method } ``` ### Database Schema for Module Registry ```prisma model Module { id String @id @default(uuid()) slug String @unique // e.g. "domaincheck" name String // display name version String category String // e.g. "domain-tools" description Json // { de: "...", en: "..." } icon String? isSystem Boolean @default(false) // built-in modules createdAt DateTime @default(now()) updatedAt DateTime @updatedAt activations TenantModuleActivation[] } model TenantModuleActivation { id String @id @default(uuid()) tenantId String moduleId String isActive Boolean @default(true) activatedAt DateTime @default(now()) module Module @relation(fields: [moduleId], references: [id]) @@unique([tenantId, moduleId]) @@index([tenantId]) } ``` ## Domaincheck Module Implementation ### Domain Availability Check Methods | Method | Pros | Cons | Recommendation | |--------|------|------|----------------| | RDAP (Registration Data Access Protocol) | Standard, JSON response, no rate limits | Not all TLDs support it | Primary method | | DNS lookup (node:dns) | Fast, no external deps, built-in | Only tells if DNS exists, not registration | Fallback/complement | | WHOIS via library | Most complete data | Rate-limited, text parsing fragile | Not needed (user chose simple status only) | **Recommended approach:** 1. Primary: DNS resolution via `node:dns/promises` — `dns.resolve(domain)`. If resolves → registered. If NXDOMAIN → likely available. 2. Enhancement: RDAP query to `https://rdap.org/domain/{domain}` for accurate registration status. For MVP, DNS resolution is sufficient and requires no external dependencies. ### TLD Variant Checking User enters "beispiel", system checks: - beispiel.de - beispiel.com - beispiel.net - beispiel.org Default TLDs stored in module config, configurable per tenant via module settings. ### API Endpoint Design ``` POST /modules/domaincheck/check Body: { domain: "beispiel" } Response: { domain: "beispiel", results: [ { tld: "de", fqdn: "beispiel.de", status: "registered" }, { tld: "com", fqdn: "beispiel.com", status: "available" }, { tld: "net", fqdn: "beispiel.net", status: "registered" }, { tld: "org", fqdn: "beispiel.org", status: "available" } ] } ``` ### Frontend Component Structure ``` modules/domaincheck/ ├── DomaincheckCard.tsx — compact card for category page ├── DomaincheckPanel.tsx — expanded view ├── components/ │ ├── DomainInput.tsx — input field + submit button │ └── ResultList.tsx — TLD results with green/red badges └── actions.ts — server action to call API ``` ## Category Page Pattern Modules grouped by category on a shared page: ``` /modules/domain-tools → shows all Domain-Tools modules as cards /modules/domain-tools/domaincheck → expanded domaincheck view ``` Category page layout: - Grid of module cards (similar to dashboard widgets) - Each card shows: icon, name, short description, "Open" button - Cards are lazy-loaded based on tenant activation ## Integration Points ### With Existing Auth System - Module endpoints protected by existing JwtAuthGuard - Tenant context from existing middleware (tenantId in JWT) - Module activation checked via ModuleGuard (new) ### With Existing Prisma/RLS - TenantModuleActivation uses tenantId for RLS - Module metadata is global (no tenant isolation needed) - Module-specific data (e.g., domaincheck history) would be tenant-scoped ### With Frontend Shell - Module category links added to sidebar (Phase 4 handles full sidebar integration) - For Phase 3: direct route `/modules/domaincheck` accessible after login - AppShell wraps module pages (header + sidebar visible) ## Risks & Mitigations | Risk | Impact | Mitigation | |------|--------|-----------| | DNS lookup rate limiting | Slow checks | Parallel lookups with Promise.allSettled, timeout per check | | Module lazy loading complexity | Build issues | Start with simple next/dynamic, test in Docker build | | Module SDK breaking changes | Future modules break | Version field in registry, SDK follows semver | | Category page with only 1 module | Looks empty | Design card to look good standalone, placeholder for future modules | ## Validation Architecture ### Unit Tests - ModuleRegistryService: CRUD operations, activation per tenant - DomaincheckService: DNS resolution mocking, TLD variant generation - ModuleGuard: activation check logic ### Integration Tests - Module activation/deactivation via API - Domaincheck endpoint with real DNS (selected safe domains) - Tenant isolation of module activations ### E2E Verification - Admin activates domaincheck module - User navigates to module, enters domain - Results display with correct free/registered status --- ## RESEARCH COMPLETE