78 lines
2.7 KiB
Markdown
78 lines
2.7 KiB
Markdown
# 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`
|