Files
tessera-ctl/.planning/phases/03-module-system-domaincheck/03-RESEARCH.md
T
schalli 6e2f6e7c3e docs(03): create phase 3 plans - Module System & Domaincheck
4 plans in 3 waves:
- 03-01: Module SDK + Registry + Activation API
- 03-02: Domaincheck backend + frontend (vertical slice)
- 03-03: Lazy Loading + Category Pages
- 03-04: Visual Verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 12:14:54 +02:00

6.9 KiB

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:

interface TesseraModule {
  id: string;
  name: string;
  version: string;
  category: string;
  description: Record<string, string>; // i18n: { de: '...', en: '...' }
  icon?: string;
  
  // Backend
  routes?: ModuleRoute[];
  
  // Frontend  
  component: () => Promise<React.ComponentType>;
  cardComponent?: () => Promise<React.ComponentType>; // compact card view
}

interface ModuleRoute {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  path: string;
  handler: string; // reference to controller method
}

Database Schema for Module Registry

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