.agent/skills/api-from-ui/SKILL.md
Skill xây dựng custom API endpoint từ UI đang hoạt động. Sử dụng khi cần (1) phân tích Screen để xác định fields cần thiết, (2) thiết kế DTO an toàn loại bỏ sensitive data, (3) tạo custom API thay thế Payload REST, (4) sync types giữa backend và frontend. Trigger khi UI đã ổn định và cần tối ưu API.
npx skillsauth add vuthuonghai-steve/KLTN-By_Thuong_Hai-Steve api-from-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Skill này cung cấp quy trình chuẩn hóa để xây dựng custom API endpoint từ UI đang sử dụng Payload REST API. Mục tiêu chính là tối ưu response size, tăng bảo mật, và chuẩn hóa workflow phát triển API.
| Tình huống | Áp dụng |
|------------|---------|
| UI đang gọi Payload REST với depth=1-2 | ✅ |
| Response chứa thông tin nhạy cảm (users, customers object) | ✅ |
| Cần giảm payload size để tối ưu performance | ✅ |
| UI đã ổn định, sẵn sàng cho production | ✅ |
| Đang develop UI mới, chưa ổn định | ❌ |
| Chỉ cần simple CRUD operations | ❌ |
| Task | Solution | Chi tiết |
|------|----------|----------|
| Phân tích Screen component | python analyze-screen.py path/to/Screen.tsx | Phase 1 |
| Tìm sensitive relationships | Check relationTo: 'users'/'customers' | Phase 2 |
| Thiết kế DTO interface | Follow {Feature}DTO pattern | Phase 3 |
| Map Payload entity → DTO | Implement mapToDTO() function | Phase 4 |
| Sync frontend types | Update service + hook | Phase 5 |
| Verify bảo mật | Check response không có users/customers | Phase 6 |
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 1: UI ANALYSIS │
│ Input: Screen.tsx, useXxxData.ts │
│ Output: Danh sách fields UI đang sử dụng │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 2: SECURITY AUDIT │
│ Input: Field list, Order.ts (collection) │
│ Output: Sensitive fields cần loại bỏ │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 3: DTO DESIGN │
│ Input: UI fields + Security audit │
│ Output: StoreOrderDTO interface │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 4: BACKEND IMPLEMENTATION │
│ Input: DTO interface │
│ Output: route.ts, service.ts, types.ts │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 5: FRONTEND INTEGRATION │
│ Input: Backend DTO │
│ Output: Updated service.ts, hook, types │
└───────────────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PHASE 6: VERIFICATION │
│ Input: API endpoint, UI │
│ Output: Verification report │
└─────────────────────────────────────────────────────────────────┘
Mục tiêu: Xác định chính xác những fields nào UI đang sử dụng từ API response.
src/screens/{Feature}Screen/
├── index.tsx # Main screen
├── hooks/
│ └── use{Feature}Data.ts # Data fetching hook ← QUAN TRỌNG
├── components/
│ ├── {Feature}Table.tsx # Sử dụng fields cho table columns
│ ├── {Feature}Dialog.tsx # Sử dụng fields cho detail view
│ ├── {Feature}Filters.tsx # Sử dụng fields cho filters
│ └── {Feature}Stats.tsx # Sử dụng fields cho statistics
└── types/
└── index.ts # Type definitions
python .agent/skills/api-from-ui/scripts/analyze-screen.py \
src/screens/Store/StoreOrdersScreen/hooks/useStoreOrdersData.ts
Script sẽ output danh sách fields:
=== Fields Detected ===
ROOT LEVEL:
- id
- code
- status
- paymentStatus
- createdAt
NESTED (deliveryAddress):
- deliveryAddress.names
- deliveryAddress.phoneNumbers
- deliveryAddress.address
ARRAY ITEMS (items):
- items[].product.name
- items[].product.type
- items[].quantity
- items[].price
| UI Field Path | Payload Source | Needed in DTO | Notes |
|---------------|----------------|---------------|-------|
| code | order.code | ✅ | Mã đơn hàng |
| status | order.status | ✅ | Trạng thái |
| deliveryAddress.names | order.deliveryAddress.names | ✅ | Tên người nhận |
| customer | order.customer | ❌ | Sensitive - loại bỏ |
| uploadedBy | order.beforeDelivery[].uploadedBy | ❌ | User object - loại bỏ |
Mục tiêu: Xác định tất cả fields nhạy cảm cần loại bỏ khỏi DTO.
Mở file collection definition (vd: src/collections/orders/Order.ts) và tìm các relationships:
// Tìm các fields có relationTo: 'users' hoặc 'customers'
{
name: 'customer',
type: 'relationship',
relationTo: 'customers', // ⚠️ SENSITIVE
}
{
name: 'uploadedBy',
type: 'relationship',
relationTo: 'users', // ⚠️ SENSITIVE
}
{
name: 'createdBy',
type: 'relationship',
relationTo: 'users', // ⚠️ SENSITIVE
}
| Field Pattern | Risk | Action |
|---------------|------|--------|
| relationTo: 'users' | Expose user data | ❌ Không trả về |
| relationTo: 'customers' | Expose customer PII | ❌ Không trả về |
| password, passwordHash | Credentials | ❌ Không trả về |
| tokens, apiKey, secret | Authentication | ❌ Không trả về |
| internalNotes, adminNotes | Internal data | ❌ Không trả về |
Một số thông tin liên hệ CÓ THỂ giữ lại nếu UI cần:
// ✅ ALLOWED - Thông tin giao hàng cơ bản
deliveryAddress: {
names: [{ fullName: string }],
phoneNumbers: [{ number: string }],
address: string,
}
// ✅ ALLOWED - Email liên hệ
customerEmail: string
// ❌ NOT ALLOWED - Full customer object
customer: Customer // Chứa thông tin nhạy cảm
Mục tiêu: Thiết kế DTO interface chỉ chứa fields UI cần, loại bỏ sensitive data.
// Pattern: {Feature}DTO
interface StoreOrderDTO { ... }
interface ProductListItemDTO { ... }
interface CustomerSummaryDTO { ... }
/**
* StoreOrderDTO - Response cho store orders API
*
* @security
* - Không chứa customer object
* - Không chứa uploadedBy (users)
* - Chỉ giữ thông tin liên hệ cơ bản từ deliveryAddress
*/
export interface StoreOrderDTO {
// Identifiers
id: string
code: string
assignmentId?: string
// Contact info (an toàn)
customerEmail: string
deliveryAddress: {
names: Array<{ fullName: string }>
phoneNumbers: Array<{ number: string }>
address: string
}
// Items (chỉ chứa thông tin cần thiết)
items: Array<{
name: string // từ product.name
type: 'bouquet' | 'single' | 'accessory'
quantity: number
price: number
totalPrice: number
commissionRate: number
}>
// Order info
shippingFee: number
createdAt: string
status: OrderStatus
paymentStatus: PaymentStatus
orderType: 'immediate' | 'scheduled' | 'recurring'
note?: string
// Optional configs
scheduledDeliveryTime?: string
recurringConfig?: RecurringConfigDTO
}
interface RecurringConfigDTO {
frequency?: 'daily' | 'weekly' | 'monthly'
daysOfWeek?: string[]
startDate?: string
endDate?: string
}
Mục tiêu: Tạo custom API endpoint với DTO mapping.
src/app/api/v1/{feature}/
├── route.ts # HTTP handler
├── service.ts # Business logic + DTO mapping
├── types.ts # DTO interfaces
└── validators.ts # Zod schemas (optional)
// service.ts
import type { Order, Product } from '@/payload-types'
import type { StoreOrderDTO } from './types'
/**
* Map Order (Payload) sang StoreOrderDTO
*
* @security
* - Loại bỏ customer, uploadedBy
* - Chỉ giữ thông tin cần cho UI
*/
function mapOrderToDTO(order: Order): StoreOrderDTO {
// Map items (loại bỏ full product object)
const items = (order.items || []).map((item) => {
let name = 'Sản phẩm'
let type: 'bouquet' | 'single' | 'accessory' = 'bouquet'
if (typeof item.product === 'object' && item.product !== null) {
const product = item.product as Product
name = product.name || 'Sản phẩm'
type = product.type || 'bouquet'
}
return {
name,
type,
quantity: item.quantity,
price: item.price,
totalPrice: item.totalPrice,
commissionRate: item.commissionRate ?? 10,
}
})
// Map deliveryAddress (chỉ giữ fields cần thiết)
const deliveryAddress = {
names: order.deliveryAddress?.names || [],
phoneNumbers: order.deliveryAddress?.phoneNumbers || [],
address: order.deliveryAddress?.address || '',
}
return {
id: order.id,
code: order.code,
customerEmail: order.customerEmail,
deliveryAddress,
items,
shippingFee: order.shippingFee,
createdAt: order.createdAt,
status: order.status,
paymentStatus: order.paymentStatus,
orderType: order.orderType,
note: order.note ?? undefined,
// ... other fields
}
}
// service.ts
export async function listStoreOrders(filters: Filters): Promise<Result> {
const payload = await getPayload({ config })
// Query orders (logic giữ nguyên)
const result = await payload.find({
collection: 'orders',
where: whereClause,
page: filters.page,
limit: filters.limit,
depth: 1, // Có thể giảm depth nếu không cần populate nhiều
})
// Map sang DTO trước khi return
const ordersDTO = result.docs.map(mapOrderToDTO)
return {
orders: ordersDTO, // ← DTO, không phải Order[]
pagination: { ... },
summary: { ... },
}
}
Mục tiêu: Sync frontend types và update hooks để sử dụng custom API.
// src/api/services/store-orders.service.ts
export interface StoreOrderDTO {
// Copy từ backend types.ts
id: string
code: string
// ...
}
export interface StoreOrdersResponse {
orders: StoreOrderDTO[]
pagination: PaginationInfo
summary: SummaryInfo
}
// src/api/services/store-orders.service.ts
export const fetchStoreOrders = async (
filters: StoreOrdersFilters
): Promise<StoreOrdersResponse> => {
const response = await ApiService.get<StoreOrdersResponse>(
`/v1/stores/store-orders?${params.toString()}`
)
return response
}
// hooks/useStoreOrdersData.ts
import type { StoreOrderDTO, StoreOrdersResponse } from '@/api/services/store-orders.service'
// Response từ API đã là DTO, không cần map thêm
const response = await fetchStoreOrders(filters)
setOrders(response.orders) // StoreOrderDTO[]
Mục tiêu: Đảm bảo API mới hoạt động đúng và an toàn.
# Gọi API và check response
curl -s "http://localhost:3000/api/v1/stores/store-orders?storeId=xxx" | jq
# Verify KHÔNG có:
# - "uploadedBy": { ... }
# - "customer": { "id": ..., "email": ..., "password": ... }
# - Bất kỳ nested users object nào
# So sánh response size
# Before (Payload REST): ~15KB per request
# After (Custom API): ~3KB per request
# Improvement: ~80% reduction
// Kiểm tra relationship đã populate chưa
function isPopulated<T>(value: string | T): value is T {
return typeof value === 'object' && value !== null
}
// Sử dụng
if (isPopulated(item.product)) {
name = item.product.name
} else {
// product là ID string
name = 'Product ID: ' + item.product
}
// Full DTO cho detail view
interface OrderDetailDTO extends OrderListDTO {
timeline: TimelineEventDTO[]
deliveryImages: ImageDTO[]
}
// Minimal DTO cho list view
interface OrderListDTO {
id: string
code: string
status: OrderStatus
total: number
}
interface ApiResponse<T> {
data: T
pagination?: PaginationInfo
summary?: SummaryInfo
}
// Usage
type ListResponse = ApiResponse<StoreOrderDTO[]>
type DetailResponse = ApiResponse<StoreOrderDTO>
// ❌ NEVER
{
customer: Customer, // Full customer object
uploadedBy: User, // User who uploaded
createdBy: User, // User who created
password: string, // Any password field
passwordHash: string, // Hashed password
tokens: Token[], // Auth tokens
apiKey: string, // API keys
internalNotes: string, // Internal notes
}
// ✅ SAFE to return
{
customerEmail: string, // Just email for contact
deliveryAddress: {
names: [{ fullName }], // Just names
phoneNumbers: [{ number }], // Just phone numbers
address: string, // Delivery address
}
}
// Giảm depth khi không cần nested relationships
await payload.find({
collection: 'orders',
depth: 0, // Chỉ lấy IDs
})
// Sử dụng select nếu collection có nhiều fields
await payload.find({
collection: 'orders',
select: {
code: true,
status: true,
createdAt: true,
// Chỉ select fields cần thiết
}
})
# Basic usage
python .agent/skills/api-from-ui/scripts/analyze-screen.py <file_path>
# Example
python .agent/skills/api-from-ui/scripts/analyze-screen.py \
src/screens/Store/StoreOrdersScreen/hooks/useStoreOrdersData.ts
# Analyze multiple files
python .agent/skills/api-from-ui/scripts/analyze-screen.py \
src/screens/Store/StoreOrdersScreen/**/*.tsx
# DTO Types template
cat .agent/skills/api-from-ui/assets/templates/types.ts.template
# Backend Service template
cat .agent/skills/api-from-ui/assets/templates/service.ts.template
# Frontend Service template
cat .agent/skills/api-from-ui/assets/templates/client-service.ts.template
# UI Fields Checklist
cat .agent/skills/api-from-ui/assets/checklists/ui-fields-checklist.md
# Security Audit Checklist
cat .agent/skills/api-from-ui/assets/checklists/security-audit-checklist.md
Skill này được kích hoạt khi user nói:
Xem thêm chi tiết tại:
references/workflow-phases.md - Chi tiết 6 phasesreferences/dto-design-patterns.md - DTO patterns và anti-patternsreferences/security-checklist.md - Security checklist đầy đủreferences/common-collections.md - Common Payload collections| Version | Date | Changes | |---------|------|---------| | 1.0.0 | 2026-02-07 | Initial release |
tools
Automates end-to-end drawing of UI screens in Pencil canvas from module spec files. Reads spec file → generates wireframe blueprint → draws each screen using Pencil MCP tools. Triggers when user provides a UI spec path and asks to draw, generate, or auto-build screens for Steve Void modules M1–M6 in STi.pen.
testing
Extracts UI Screen Specs by analyzing Schema and Diagrams. Use when you need to bridge database logic and flow diagrams into intermediate UI component specifications for a given module. Trigger when user says "analyze UI for module X", "generate ui spec", "phân tích UI module", or invokes "ui-architecture-analyst --module M[X]".
development
Giải thích lỗi TypeScript một cách dễ hiểu bằng tiếng Việt. Sử dụng khi gặp lỗi type, generic, inference, hoặc bất kỳ lỗi TS nào cần được giải thích rõ ràng.
development
Skill phan tach yeu cau/tinh nang thanh cac phase, task va subtask cu the. Tao bo tai lieu planning clean, khong chua code mau, tap trung mo ta logic va nghiep vu. Su dung khi: (1) nhan yeu cau tinh nang moi can lap ke hoach, (2) co tai lieu nghien cuu can chuyen thanh task plan, (3) nguoi dung yeu cau phan tach cong viec, (4) can tao roadmap trien khai cho du an/tinh nang. Trigger: /task-planner, /plan-tasks, "phan tach task", "lap ke hoach", "tao plan", "chia phase".