feat(ADM-018): completed feature
This commit is contained in:
77
work/artifacts/ADM-018/architect.md
Normal file
77
work/artifacts/ADM-018/architect.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# ADM-018 Design — Customer List
|
||||
|
||||
## Problem
|
||||
`GET /users` only returns `users_profiles` fields (`userId`, `displayName`, `phone`).
|
||||
The admin frontend needs `email`, `role`, `createdAt` from `identity_users`, plus search by email, pagination, and total count.
|
||||
|
||||
## Design
|
||||
|
||||
### Backend changes (users module)
|
||||
|
||||
**Domain layer** — extend `Profile` with optional `email` and `role`:
|
||||
```ts
|
||||
// profile.ts — add:
|
||||
export interface CustomerSummary {
|
||||
userId: string;
|
||||
email: string;
|
||||
role: string;
|
||||
displayName: string | null;
|
||||
phone: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface CustomerListResult {
|
||||
items: CustomerSummary[];
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
**Port** — add new method to `ProfileRepository`:
|
||||
```ts
|
||||
listCustomers(opts: { q?: string; offset: number; limit: number }): Promise<CustomerListResult>;
|
||||
```
|
||||
|
||||
**Infrastructure** — `PgProfileRepository.listCustomers()`:
|
||||
```sql
|
||||
SELECT iu.id, iu.email, iu.role, up.display_name, up.phone, iu.created_at
|
||||
FROM identity_users iu
|
||||
LEFT JOIN users_profiles up ON up.user_id = iu.id
|
||||
WHERE ($1::text IS NULL OR iu.email ILIKE '%' || $1 || '%')
|
||||
ORDER BY iu.created_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
```
|
||||
Plus a `COUNT(*)` query for total.
|
||||
|
||||
**Application** — new use case `ListCustomers`:
|
||||
```ts
|
||||
export class ListCustomers {
|
||||
constructor(private readonly profiles: ProfileRepository) {}
|
||||
async execute(opts): Promise<CustomerListResult> { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**API** — enhance `GET /users`:
|
||||
- Query params: `q` (optional string), `offset` (default 0), `limit` (default 20)
|
||||
- Response: `{ items: CustomerSummary[], total: number }`
|
||||
- Serialize `userId` as `id` to match frontend `Customer` type
|
||||
|
||||
### Frontend changes
|
||||
|
||||
**api-client.ts** — `customersApi.list()` already passes `offset`, `limit`, `q`. Update return type to include `total`.
|
||||
|
||||
**customers/page.tsx** — set `total` from API response: `setTotal(data.total)`.
|
||||
|
||||
## Boundaries
|
||||
- No new module — stays in users module
|
||||
- No migration needed — `identity_users` already has `email`, `role` (migration 003)
|
||||
- Domain layer stays DB-free (ports pattern)
|
||||
- Admin-only endpoint (already gated by `requireRole('admin')`)
|
||||
|
||||
## Files to change
|
||||
1. `project/src/modules/users/domain/profile.ts` — add `CustomerSummary`, `CustomerListResult`
|
||||
2. `project/src/modules/users/domain/ports.ts` — add `listCustomers()` to `ProfileRepository`
|
||||
3. `project/src/modules/users/application/profile-use-cases.ts` — add `ListCustomers`
|
||||
4. `project/src/modules/users/infrastructure/pg-profile-repository.ts` — implement `listCustomers()`
|
||||
5. `project/src/modules/users/api/users.routes.ts` — enhance `GET /users`
|
||||
6. `project/apps/admin/src/lib/api-client.ts` — update return type
|
||||
7. `project/apps/admin/src/app/(dashboard)/customers/page.tsx` — wire `total`
|
||||
Reference in New Issue
Block a user