website refactor

This commit is contained in:
2026-01-13 01:36:27 +01:00
parent d18e2979ba
commit e981ebd9e9
5 changed files with 623 additions and 96 deletions

View File

@@ -110,7 +110,7 @@ Rules:
- MUST be side-effect free.
- MUST NOT call HTTP.
- MUST NOT call the API.
- Input: API Transport DTO
- Input: `Result<ApiDto, string>` or `ApiDto`
- Output: ViewModel
See [`BUILDERS.md`](docs/architecture/website/BUILDERS.md:1).
@@ -131,7 +131,7 @@ Rules:
- MUST be side-effect free.
- MUST NOT call HTTP.
- MUST NOT call the API.
- Input: API Transport DTO
- Input: `Result<ApiDto, string>` or `ApiDto`
- Output: ViewData
See [`BUILDERS.md`](docs/architecture/website/BUILDERS.md:1).
@@ -140,6 +140,26 @@ Canonical placement in this repo:
- `apps/website/lib/builders/view-data/**`
### 4.3 Result Type
Definition: Type-safe error handling for all operations.
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
- Never throw exceptions
See [`Result.ts`](apps/website/lib/contracts/Result.ts:1).
Canonical placement in this repo:
- `apps/website/lib/contracts/Result.ts`
### 4.3 Display Object
Definition: deterministic, reusable, UI-only formatting/mapping logic.
@@ -163,12 +183,14 @@ Canonical placement in this repo:
```text
RSC page.tsx
PageQuery
PageQuery.execute()
API client (infra)
API Transport DTO
Result<ApiDto, string>
ViewData Builder (lib/builders/view-data/)
ViewData
@@ -184,6 +206,8 @@ API client (useEffect)
API Transport DTO
Result<ApiDto, string>
ViewModel Builder (lib/builders/view-models/)
ViewModel (lib/view-models/)
@@ -206,68 +230,53 @@ Allowed:
- client submits intent (FormData, button action)
- server action performs UX validation
- **server action calls a service** (not API clients directly)
- service orchestrates API calls and business logic
- **server action calls a mutation** (not services directly)
- mutation orchestrates services for writes
**Server Actions must use Services:**
**Server Actions must use Mutations:**
```typescript
// ❌ WRONG - Direct API client usage
// ❌ WRONG - Direct service usage
'use server';
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import { AdminService } from '@/lib/services/admin/AdminService';
export async function updateUserStatus(userId: string, status: string) {
const apiClient = new AdminApiClient(...);
await apiClient.updateUserStatus(userId, status); // ❌ Should use service
const service = new AdminService(...);
await service.updateUserStatus(userId, status); // ❌ Should use mutation
}
// ✅ CORRECT - Service usage
// ✅ CORRECT - Mutation usage
'use server';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import { AdminService } from '@/lib/services/admin/AdminService';
import { AdminUserMutation } from '@/lib/mutations/admin/AdminUserMutation';
import { revalidatePath } from 'next/cache';
export async function updateUserStatus(userId: string, status: string) {
try {
// Create infrastructure
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
const service = new AdminService(apiClient);
// Use service
await service.updateUserStatus(userId, status);
// Revalidate
revalidatePath('/admin/users');
} catch (error) {
console.error('updateUserStatus failed:', error);
const mutation = new AdminUserMutation();
const result = await mutation.updateUserStatus(userId, status);
if (result.isErr()) {
console.error('updateUserStatus failed:', result.getError());
throw new Error('Failed to update user status');
}
revalidatePath('/admin/users');
}
```
**Pattern**:
1. Server action creates infrastructure (logger, errorReporter, apiClient)
2. Server action creates service with infrastructure
3. Server action calls service method
4. Server action handles revalidation and returns
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
**Rationale**:
- Services orchestrate API calls (can grow to multiple calls)
- Keeps server actions consistent with PageQueries
- 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
- Services can add caching, retries, transformations
See [`FORM_SUBMISSION.md`](docs/architecture/website/FORM_SUBMISSION.md:1) and [`SERVICES.md`](docs/architecture/website/SERVICES.md:1).
See [`MUTATIONS.md`](docs/architecture/website/MUTATIONS.md:1) and [`SERVICES.md`](docs/architecture/website/SERVICES.md:1).
## 7) Authorization (strict)
@@ -333,10 +342,11 @@ See [`WEBSITE_DI_RULES.md`](docs/architecture/website/WEBSITE_DI_RULES.md:1).
3. API Transport DTOs never reach Templates.
4. Templates accept ViewData only.
5. Page Queries do not format; they only compose.
6. ViewData Builders transform API DTO → ViewData (RSC).
7. ViewModel Builders transform API DTO → ViewModel (Client).
8. Builders are pure and deterministic.
9. Server Actions are the only write entry point.
10. Server Actions must use Mutations (not Services directly).
11. Mutations orchestrate Services for writes.
12. Authorization always belongs to the API.
6. All operations return `Result<T, E>` for type-safe error handling.
7. ViewData Builders transform API DTO → ViewData (RSC).
8. ViewModel Builders transform API DTO → ViewModel (Client).
9. Builders are pure and deterministic.
10. Server Actions are the only write entry point.
11. Server Actions must use Mutations (not Services directly).
12. Mutations orchestrate Services for writes.
13. Authorization always belongs to the API.