website refactor
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user