6e2f6e7c3e
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>
210 lines
6.9 KiB
Markdown
210 lines
6.9 KiB
Markdown
# 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<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
|
|
|
|
```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
|