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>
This commit is contained in:
@@ -19,12 +19,15 @@ Modulares Plugin-System mit versioniertem SDK (@tessera/sdk), datenbankgestuetzt
|
|||||||
- **D-03:** TLD-Liste soll konfigurierbar sein (Standard-TLDs vorausgewaehlt, erweiterbar).
|
- **D-03:** TLD-Liste soll konfigurierbar sein (Standard-TLDs vorausgewaehlt, erweiterbar).
|
||||||
|
|
||||||
### Modul-Darstellung
|
### Modul-Darstellung
|
||||||
- **D-04:** Claude entscheidet das UI-Pattern fuer die Modul-Darstellung basierend auf der bestehenden Architektur (Next.js App Router, Route Groups, Lazy Loading).
|
- **D-04:** Module werden in Kategorien gruppiert (z.B. "Domain-Tools"). Eine Kategorie-Seite zeigt alle Module dieser Kategorie im Hauptbereich — nicht ein einzelnes kleines Modul auf der ganzen Seite.
|
||||||
|
- **D-05a:** Einheitliches Look & Feel: Module sollen als Karten/Panels innerhalb der Kategorie-Seite erscheinen, sodass auch kleine Module nicht verloren wirken.
|
||||||
|
- **D-05b:** Bei Bedarf kann ein Modul auch eine erweiterte/Vollbild-Ansicht haben (z.B. wenn der Benutzer es oeffnet), aber die Standard-Ansicht ist kompakt in der Kategorie-Uebersicht.
|
||||||
|
|
||||||
### Module SDK
|
### Module SDK
|
||||||
- **D-05:** Modul-Interface als `@tessera/sdk` Package im Monorepo (packages/sdk oder packages/module-sdk).
|
- **D-06:** Modul-Interface als `@tessera/sdk` Package im Monorepo (packages/sdk oder packages/module-sdk).
|
||||||
- **D-06:** Module werden per Datenbank-Registry verwaltet — kein Filesystem-Scanning.
|
- **D-07:** Module werden per Datenbank-Registry verwaltet — kein Filesystem-Scanning.
|
||||||
- **D-07:** Aktivierung/Deaktivierung pro Mandant durch Admin ohne Neustart (Requirement MOD-03, MRKT-02).
|
- **D-08:** Aktivierung/Deaktivierung pro Mandant durch Admin ohne Neustart (Requirement MOD-03, MRKT-02).
|
||||||
|
- **D-09:** Jedes Modul gehoert zu einer Kategorie (z.B. "Domain-Tools", "Utilities"). Kategorie ist Teil der Modul-Metadaten im SDK.
|
||||||
|
|
||||||
### Claude's Discretion
|
### Claude's Discretion
|
||||||
- Modul-UI-Pattern: eigene Seite vs. Panel vs. anderes — basierend auf bestehender App Router Architektur
|
- Modul-UI-Pattern: eigene Seite vs. Panel vs. anderes — basierend auf bestehender App Router Architektur
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# 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
|
||||||
Reference in New Issue
Block a user