website refactor

This commit is contained in:
2026-01-13 02:42:58 +01:00
parent 38b25bafe1
commit b82736b24b
6 changed files with 661 additions and 251 deletions

View File

@@ -148,10 +148,11 @@ Purpose: eliminate exceptions and provide explicit error paths.
Rules:
- All PageQueries return `Result<ApiDto, string>`
- All Mutations return `Result<void, string>`
- Use `ResultFactory.ok(value)` for success
- Use `ResultFactory.error(message)` for errors
- All PageQueries return `Result<ViewData, PresentationError>`
- All Mutations return `Result<void, MutationError>`
- All Services return `Result<ApiDto, DomainError>`
- Use `Result.ok(value)` for success
- Use `Result.err(error)` for errors
- Never throw exceptions
See [`Result.ts`](apps/website/lib/contracts/Result.ts:1).
@@ -183,13 +184,19 @@ Canonical placement in this repo:
```text
RSC page.tsx
PageQuery.execute()
PageQuery (manual construction)
API client (infra)
Service (creates own API Client, Logger, ErrorReporter)
API Client (makes HTTP calls)
API Transport DTO
Result<ApiDto, string>
Result<ApiDto, DomainError>
PageQuery (maps DomainError → PresentationError)
Result<ViewData, PresentationError>
ViewData Builder (lib/builders/view-data/)
@@ -198,6 +205,13 @@ ViewData
Template
```
**Key Points:**
- PageQuery constructs Service
- Service creates its own dependencies
- Service returns Result<ApiDto, DomainError>
- PageQuery maps errors to presentation layer
- Builder transforms API DTO to ViewData
### Client Components
```text
Client Component
@@ -241,40 +255,55 @@ Allowed:
import { AdminService } from '@/lib/services/admin/AdminService';
export async function updateUserStatus(userId: string, status: string) {
const service = new AdminService(...);
const service = new AdminService();
await service.updateUserStatus(userId, status); // ❌ Should use mutation
}
// ✅ CORRECT - Mutation usage
'use server';
import { AdminUserMutation } from '@/lib/mutations/admin/AdminUserMutation';
import { UpdateUserStatusMutation } from '@/lib/mutations/UpdateUserStatusMutation';
import { revalidatePath } from 'next/cache';
export async function updateUserStatus(userId: string, status: string) {
const mutation = new AdminUserMutation();
const result = await mutation.updateUserStatus(userId, status);
export async function updateUserStatus(input: UpdateUserStatusInput) {
const mutation = new UpdateUserStatusMutation();
const result = await mutation.execute(input);
if (result.isErr()) {
console.error('updateUserStatus failed:', result.getError());
throw new Error('Failed to update user status');
console.error('updateUserStatus failed:', result.error);
return { success: false, error: result.error };
}
revalidatePath('/admin/users');
return { success: true };
}
```
**Pattern**:
1. Server Action (thin wrapper) - handles framework concerns (revalidation)
2. Mutation (framework-agnostic) - creates infrastructure, calls service
3. Service (business logic) - orchestrates API calls
4. API Client (infrastructure) - makes HTTP requests
5. Result - type-safe error handling
1. **Server Action** (thin wrapper) - handles framework concerns (revalidation, returns to client)
2. **Mutation** (framework-agnostic) - constructs Service, calls service methods
3. **Service** - constructs own dependencies (API Client, Logger), returns Result
4. **API Client** (infrastructure) - makes HTTP requests, throws HTTP errors
5. **Result** - type-safe error handling at every layer
**Dependency Flow**:
```
Server Action
↓ (constructs)
Mutation
↓ (constructs)
Service (creates API Client, Logger, ErrorReporter)
↓ (calls)
API Client
↓ (HTTP)
Backend API
```
**Rationale**:
- Mutations are framework-agnostic (can be tested without Next.js)
- Consistent pattern with PageQueries
- Type-safe error handling with Result
- Makes infrastructure explicit and testable
- Manual construction (no DI container issues)
See [`MUTATIONS.md`](docs/architecture/website/MUTATIONS.md:1) and [`SERVICES.md`](docs/architecture/website/SERVICES.md:1).