prompts.chatprompts.chatprompts.chat
BotPromptKỹ năngQuy trìnhDanh mụcThẻCao thủ PromptPhong cách
SáchCho trẻ emNhà phát triển
Đăng nhập
CC0 2026 prompts.chat
DeepWikiHướng dẫn...Tài liệuAPIQuyền riêng tưĐiều khoảnHỗ trợGiới thiệuGitHub
Tất cả thẻ

Prompt Web Development

92 prompt
Trang cổng thanh toán
Văn bản

Hướng dẫn người dùng quy trình tích hợp cổng thanh toán vào ứng dụng web, đảm bảo giao dịch an toàn và trải nghiệm người dùng liền mạch bằng cách sử dụng Notion

Hãy tạo một trang đích (landing page) hoàn chỉnh, được chăm chút tỉ mỉ và có thể sao chép-dán trực tiếp, được thiết kế để đóng vai trò như một trung tâm thanh toán tất cả trong một (all-in-one payment hub), tận dụng tối đa sự linh hoạt và tính năng phong phú của Notion. Trang được tuyển chọn tuyệt vời này cần tích hợp liền mạch hàng loạt tùy chọn thanh toán — hãy nghĩ đến PayPal, Stripe, PayEx, GRA, GrabPay Later, Shopee, Shopee PayLater, AhaPay và HitPay — quy tụ đa dạng các kênh tài chính vào trong một trải nghiệm gắn kết duy nhất.

Hãy tưởng tượng một khu chợ số sôi động, nơi các nhà sáng tạo có thể dễ dàng trưng bày sản phẩm/dịch vụ của mình, được thiết kế để thu hút và giữ chân người dùng. Khu vực được thiết kế chu đáo dành cho thành viên miễn phí sẽ trao quyền cho các nhà sáng tạo đồng nghiệp, mang lại cho họ khả năng độc đáo là chỉ cần dán đường liên kết của riêng mình vào, tạo ra sự kết nối tức thì tới những giá trị tinh hoa mà họ muốn chia sẻ.

Nhiệm vụ của bạn là khơi gợi một cảm giác mới mẻ đầy phấn khích, giống như sự hào hứng kỳ diệu khi bước ra khỏi những khuôn khổ quen thuộc nhàm chán thường ngày. Từng yếu tố trên trang đích này phải thổi luồng sinh khí mới vào những điều bình thường, biến các giao dịch hàng ngày thành những khoảnh khắc khơi dậy sự tò mò và tương tác. Với hàng loạt tùy chọn được bố trí dưới định dạng bắt mắt và thân thiện với người dùng, khách truy cập sẽ cảm nhận được sự háo hức mong chờ khi điều hướng qua bản giao hưởng của những lựa chọn sẵn có dành cho họ.

Hãy để trang web phản chiếu một bầu không khí cuốn hút nơi sự đơn điệu tan biến, mang đến một góc nhìn tươi mới cho quy trình thanh toán truyền thống. Đây không chỉ đơn thuần là một trang đích; đó là một lời mời gọi khám phá, kết nối và trải nghiệm tiềm năng năng động của thị trường kỹ thuật số qua từng cú nhấp chuột. Hãy đắm mình vào nghệ thuật thiết kế và công năng — suy cho cùng, trong bức tranh giao dịch trực tuyến không ngừng phát triển này, ai lại không muốn cảm nhận luồng sinh khí đầy hứng khởi đó?
Web DevelopmentAPIIntegration+2
A@adamsofea1111
0
Thiết kế trang Giới thiệu bản thân (About Me) theo phong cách Glassmorphism
Văn bản

Tạo một trang 'About Me' (Giới thiệu bản thân) tinh tế và tương thích đa thiết bị theo phong cách thiết kế kính mờ (Glassmorphism), phù hợp cho cả giao diện máy tính lẫn điện thoại di động, kèm bảng quản trị để quản lý nội dung.

Hãy đóng vai một nhà thiết kế web. Bạn được giao nhiệm vụ tạo một trang 'About Me' (Giới thiệu bản thân) vừa bắt mắt về mặt thị giác vừa đảm bảo công năng sử dụng. Trang của bạn cần áp dụng các nguyên tắc thiết kế kính mờ (Glassmorphism) với tông màu ấm sáng, gợi cảm giác gần gũi như nét bút trên trang giấy. Hãy đảm bảo trang web hiển thị tương thích tốt (responsive), hoạt động mượt mà trên cả máy tính để bàn và thiết bị di động.

Trang của bạn sẽ bao gồm:
- Khu vực giới thiệu bản thân với các phân mục khung sườn (blueprint sections) có thể tùy chỉnh để cập nhật bổ sung dần theo thời gian.
- Các tùy chọn tích hợp để chèn liên kết kênh Telegram.
- Các tính năng thân thiện với khách truy cập công khai nhằm tăng cường mức độ tương tác của người dùng.

Bạn sẽ thực hiện:
- Thiết kế một bảng quản trị (admin panel) giúp quản lý nội dung dễ dàng, cho phép cập nhật nội dung mà không cần tính năng đăng nhập của người dùng cuối.
- Sử dụng các bộ phông chữ tiếng Ba Tư (Persian fonts) chuẩn web phù hợp cho thiết kế giao diện web.
- Đảm bảo tổng thể thiết kế sạch sẽ, cuốn hút và bắt mắt.

Quy tắc:
- Không có tính năng đăng nhập cho người dùng thường.
- Duy trì sự tối giản, dễ dùng đồng thời thể hiện tính thẩm mỹ thiết kế cao cấp.
Web DevelopmentDesignCreative
I@icymost
0
Cổng thông tin Quản trị viên cho Công cụ Tự động Đổi tên Tệp
Văn bản

Phát triển Cổng thông tin Quản trị viên an toàn và tương thích đa thiết bị (responsive) bằng Google Apps Script để quản lý Công cụ Tự động Đổi tên Tệp. Cổng thông tin này xử lý việc tải lên tài liệu hàng loạt và đổi tên tệp dựa trên thông tin nhân viên.

Hãy đóng vai một nhà phát triển web được giao nhiệm vụ xây dựng Cổng thông tin Quản trị viên (Administrator Portal) hiện đại cho một Công cụ Tự động Đổi tên Tệp. Nhiệm vụ của bạn là phát triển một giao diện nền tảng web an toàn, tương thích đa thiết bị (responsive) sử dụng Google Apps Script, HTML, CSS và JavaScript.

Trách nhiệm của bạn bao gồm:
- Triển khai tính năng đăng nhập quản trị viên an toàn với cơ chế quản lý phiên làm việc (session management) và tự động hết hạn phiên khi không hoạt động (timeout).
- Tạo bảng điều khiển (dashboard) để hiển thị các chỉ số thống kê như tổng số bản ghi CSV đã tải lên, tổng số tệp đã tải lên, số tệp đổi tên thành công, số tệp không khớp, các trường hợp khớp trùng lặp, trạng thái xử lý, lịch sử tải xuống và hoạt động gần đây.
- Thiết kế hệ thống đổi tên tệp có khả năng đối khớp thông tin nhân viên từ tệp CSV bằng cách kết hợp bất kỳ hai trường dữ liệu nào (Mã nhân viên / Employee ID, Tên / First Name, Tên đệm / Middle Name, hoặc Họ / Surname).
- Cho phép quản trị viên tự định nghĩa mẫu quy tắc đặt tên tệp (renaming template).
- Tạo tệp nén ZIP chứa các tệp đã đổi tên thành công theo quy ước đặt tên: `SalarySlips_Renamed_month_year.zip`.
- Xuất báo cáo xử lý kèm theo số liệu thống kê chi tiết và danh sách lỗi, hỗ trợ xuất ra định dạng Excel và CSV.

Quy tắc và Ràng buộc:
- Đảm bảo tất cả các tệp tải lên (PDF và JPG) đều được đổi tên theo đúng mẫu đã thiết lập.
- Xử lý lỗi bằng cách ghi nhật ký (log) và đưa danh sách các tệp bị lỗi/bỏ qua vào trong báo cáo.
- Duy trì giao diện người dùng gọn gàng, trực quan và chuyên nghiệp.
- Cung cấp tùy chọn tải xuống tệp nén ZIP và các báo cáo xử lý sau khi hoàn tất quá trình.

Bạn sẽ sử dụng các biến như `month` và `year` trong quy tắc đặt tên tệp để đảm bảo tính linh hoạt.
Web DevelopmentAutomationDashboard
R@ramsham355
0
Kiểm Toán Luồng Thanh Toán Dựa Trên Clarity
Văn bản
# Kiểm Toán Luồng Giỏ Hàng & Thanh Toán Dựa Trên Clarity

## Cấu hình (điền thông tin trước khi sử dụng)
- CLARITY_MCP: clarity
- START_DATE: {date:last 14 days}
- PAGES: {pages:"/sepet", "/odeme", "/sonuc"}
- DEVICES: device
- PAYMENT_MODEL: payment_model
- AUXILIARY_TOOLS (tùy chọn — xóa bất kỳ dòng nào bạn không sử dụng):
  - Theo dõi lỗi (ví dụ: Sentry, Bugsnag, Rollbar): error_tracking_mcp
  - Hiệu năng/APM (ví dụ: New Relic, Datadog, Grafana): performance_mcp
  - Nhật ký hệ thống - Logs (ví dụ: Elastic, Loki): logs_mcp
  - Phân tích/phễu chuyển đổi (ví dụ: GA4, Mixpanel, Amplitude): analytics_mcp
  - Công cụ khác: other_mcps
- TECHNICAL_NOTES (tùy chọn): [stack, known constraints, special business rules]

## Vai trò
Bạn là một chuyên gia phân tích tỷ lệ chuyển đổi/UX thương mại điện tử. Mục tiêu: xác định mọi yếu tố
gây chặn, làm chậm hoặc khiến người dùng từ bỏ luồng từ giỏ hàng → địa chỉ/vận chuyển →
thanh toán → xác nhận đơn hàng, đồng thời lập một báo cáo sắp xếp theo mức độ ưu tiên mà bất kỳ ai
trong đội ngũ cũng có thể đọc hiểu.

## Nguyên tắc sử dụng công cụ
- CLARITY_MCP là nguồn dữ liệu chính. Các bản ghi phiên truy cập (session recordings), bản đồ nhiệt (heatmaps) và các tín hiệu
  tự động (rage click, dead click, quick back, lỗi JS, cuộn trang quá mức) đều được lấy
  từ đây. Mọi phát hiện bắt buộc phải có ít nhất một phiên Clarity làm bằng chứng.
- Các công cụ phụ trợ dùng để xác minh và tìm nguyên nhân gốc rễ, không dùng để khám phá ban đầu:
  - Theo dõi lỗi (Error tracking) → khi Clarity cho thấy có lỗi JS hoặc hành vi "không có gì xảy ra",
    hãy tìm kiếm các sự cố khớp trong cùng khung thời gian và trang đó;
    thu thập stack trace, số lượng người dùng bị ảnh hưởng, lần xuất hiện đầu tiên/cuối cùng, và bản phát hành (release).
  - APM → khi triệu chứng là "chậm / vòng xoay chờ tải / hết thời gian chờ (timeout)", hãy kiểm tra các điểm cuối
    (endpoints) liên quan trong cùng khung thời gian để xem độ trễ, tỷ lệ lỗi hoặc sự sụt giảm thông lượng.
  - Nhật ký (Logs) → kiểm tra các lỗi máy chủ hoặc các lệnh từ chối do quy tắc nghiệp vụ khớp với
    dấu thời gian của một phiên truy cập cụ thể.
  - Phân tích (Analytics) → đối chiếu xác nhận dữ liệu phễu của Clarity với một nguồn thứ hai.
- Khi tìm kiếm dữ liệu khớp, hãy thu hẹp khung thời gian trong khoảng ±5 phút xung quanh phiên Clarity
  đó, lọc theo trang/tuyến đường (route) và theo thiết bị/trình duyệt nếu có thể.
- Nếu một công cụ chưa được cấu hình, không thể kết nối hoặc không trả về kết quả nào, hãy ghi rõ "không
  thể xác minh bằng X" trong báo cáo và tiếp tục. Không cố gắng sử dụng một
  công cụ chưa được cấu hình.

## Quy trình làm việc
1. Bức tranh tổng thể: đối với PAGES, hãy lấy tổng số phiên, tỷ lệ lỗi JS, tỷ lệ rage/dead click,
   tỷ lệ quick back và thời gian trung bình trên trang. Xác định bước chuyển tiếp
   có tỷ lệ rơi rụng (drop-off) cao nhất.
2. Sử dụng bộ lọc tín hiệu để liệt kê các phiên trên những trang liên quan; bắt đầu
   từ nơi có mật độ tín hiệu cao nhất, xem xét ít nhất 20 bản ghi phiên. Nếu có ít
   hơn 20 bản ghi, hãy xem toàn bộ.
3. Đối với mỗi bản ghi, hãy ghi chú: bước thực hiện, ý định của người dùng, chuyện gì đã xảy ra (báo lỗi,
   nút bấm không phản hồi, vòng lặp, chuyển hướng bất ngờ, giỏ hàng bị đặt lại về trống), người dùng
   đã làm gì tiếp theo, dấu thời gian, thiết bị/trình duyệt.
4. Chuyển các quan sát đơn lẻ thành các mẫu lặp lại (patterns); nếu cùng một vấn đề xuất hiện trong
   nhiều bản ghi, hãy gộp chung thành một phát hiện và nêu rõ có bao nhiêu phiên
   gặp tình trạng đó.
5. Xác minh chéo từng mẫu lặp lại với các công cụ phụ trợ đã cấu hình. Nếu không
   tìm thấy dữ liệu khớp, hãy ghi rõ như vậy; phát hiện đó vẫn được giữ nguyên và nguyên nhân gốc rễ ghi là "chưa rõ" (unknown).
6. Kiểm tra theo chiều ngược lại: nếu một công cụ phụ trợ cho thấy một đợt tăng vọt rõ rệt hoặc một
   sự cố mới có tần suất cao trong kỳ, hãy tìm các phiên Clarity
   trùng khớp với thời điểm đó và báo cáo tác động thực tế lên người dùng.
7. Chủ động tìm kiếm các trường hợp sau:
   - Các phiên mà bước chuyển tiếp sang màn hình thanh toán không bao giờ kích hoạt hoặc bị trễ
   - Nếu PAYMENT_MODEL là redirect/iframe: các lỗi, trang trắng hoặc giỏ hàng bị trống rỗng
     khi quay trở lại từ cổng thanh toán (việc bản ghi tự ngắt ngay tại thời điểm chuyển hướng
     sang bên thứ ba là bình thường và không tính là một phát hiện lỗi)
   - Lỗi kiểm tra hợp lệ biểu mẫu bị ẩn (người dùng nhấn "tiếp tục" nhưng không có gì xảy ra)
   - Nhấp từ 3 lần trở lên vào nút thanh toán/tiếp tục mà không chuyển bước
   - Tổng tiền giỏ hàng không cập nhật sau khi thay đổi giỏ hàng, hoặc giỏ hàng tự dưng bị làm trống
   - Không thể chạm tới nút kêu gọi hành động (CTA) trên điện thoại do bàn phím ảo hoặc thanh cố định dưới đáy che khuất
   - Bị kẹt ở bước nhập mã giảm giá/khuyến mãi
   - Bị đẩy qua lại hoặc rơi vào vòng lặp ở màn hình chọn đăng nhập/mua hàng không cần tài khoản
   - Thanh toán có vẻ đã thành công nhưng không hiện xác nhận đơn hàng (luôn xếp mức P0)

## Phân loại mức độ ưu tiên
- P0: chặn hoàn toàn luồng thanh toán và lặp lại nhiều lần
- P1: làm cho việc hoàn tất đơn hàng trở nên khó khăn hơn đáng kể
- P2: gây cản trở nhưng vẫn có thể hoàn tất đơn hàng
- P3: lỗi thẩm mỹ giao diện
Đối với mọi phát hiện, hãy nêu rõ mức độ phổ biến (tỷ lệ % và phân bổ theo thiết bị). Các phát hiện được xác minh
thêm bởi một công cụ phụ trợ có độ tin cậy cao hơn một bậc so với các phát hiện chỉ dựa trên Clarity;
hãy thể hiện điều này trong cột "mức độ bằng chứng" (Chỉ Clarity / Clarity + tool_name).
Những vấn đề chỉ xuất hiện trong một phiên duy nhất nhưng có vẻ nghiêm trọng sẽ được đưa vào danh sách "Cần
xác minh" (To verify) riêng biệt — không trộn lẫn chúng vào nhóm P0.

## Định dạng báo cáo
Tệp lưu: `reports/checkout-audit-<date>.md`, tiếng Anh, định dạng markdown.
1. Tóm tắt điều hành (Executive summary) — ngôn ngữ phi kỹ thuật, tối đa 5 gạch đầu dòng:
   vấn đề → số lượng người dùng bị ảnh hưởng → tác động kinh doanh ước tính
2. Bảng tỷ lệ rơi rụng theo luồng (Flow drop-off table) — bước → số lượng phiên → % đi tiếp sang bước kế tiếp
3. Bảng tổng hợp phát hiện (Findings table) —
   ID | Mức ưu tiên | Bước | Vấn đề (ngôn ngữ dễ hiểu) | Số phiên bị ảnh hưởng | Thiết bị | Mức độ bằng chứng | ID phiên ví dụ
4. Phụ lục dành cho lập trình viên (Developer appendix) — ứng với từng phát hiện: quan sát thực tế, bằng chứng từ các công cụ
   phụ trợ (liên kết sự cố, dữ liệu điểm cuối/độ trễ, dòng nhật ký — nếu có), nguyên nhân
   kỹ thuật có khả năng xảy ra (được gắn nhãn rõ là diễn giải), component/điểm cuối nghi ngờ,
   hành động khuyến nghị, cách thức xác minh lại
5. Danh sách cần xác minh thêm (To-verify list)
6. Hạn chế về dữ liệu (Data limitations) — những công cụ nào không thể kiểm tra, cỡ mẫu nhỏ,
   điểm mù dữ liệu, các giới hạn của bộ lọc ngày tháng

## Quy tắc
- Tuyệt đối không viết ra một phát hiện nếu không có bản ghi phiên (recording) làm bằng chứng.
- Tách biệt rõ giữa quan sát thực tế và phần diễn giải: "đã nhấp 4 lần, trang không
  thay đổi" là một quan sát; "có khả năng do API hết thời gian chờ (timeout)" là một sự diễn giải —
  hãy gắn nhãn rõ ràng như vậy.
- Trình bày sự trùng khớp giữa các công cụ dưới dạng "khung thời gian giao nhau", tuyệt đối không khẳng định ngay đó là quan hệ nhân quả.
- Nếu thiếu dữ liệu, hãy nêu rõ điều đó; không lấp đầy khoảng trống bằng sự phỏng đoán. Nếu
  không rõ nguyên nhân kỹ thuật, hãy viết là "unknown, needs developer review" (chưa rõ, cần lập trình viên kiểm tra).
- Giữ nguyên chính xác các ID phiên, ID sự cố, đường dẫn điểm cuối (endpoint paths) và các URL như bản gốc.
- Lưu trữ dữ liệu thô kéo về từ mỗi công cụ trong thư mục `reports/raw/<tool>/`.
Web Development
A@ahmetkorkmaz3
0
Dịch Vụ Rút Ngắn URL — Ứng Dụng Full-Stack
Văn bản

Câu lệnh này cung cấp bản thiết kế toàn diện để xây dựng một dịch vụ rút ngắn URL full-stack an toàn. Nó quy định một giao diện người dùng (frontend) gọn nhẹ bằng vanilla JS và một hệ thống phụ trợ (backend) RESTful mạnh mẽ. Các tính năng chính bao gồm bí danh tùy chỉnh, mã QR, phân tích chi tiết, thời hạn liên kết, bảo vệ bằng mật khẩu và các biện pháp bảo mật nghiêm ngặt, đảm bảo hiệu năng cao và khả năng tiếp cận theo chuẩn WCAG.

## 🚀 Dịch Vụ Rút Ngắn URL — Ứng Dụng Full-Stack

### 📋 Tổng Quan Dự Án
Xây dựng một dịch vụ rút ngắn URL full-stack sẵn sàng cho môi trường thực tế (production-ready) với giao diện người dùng hiện đại, dễ tiếp cận và hệ thống RESTful backend API mạnh mẽ. Ứng dụng phải xử lý trọn vẹn vòng đời của các liên kết rút gọn: khởi tạo, chuyển hướng, theo dõi, quản lý và hết hạn. Ưu tiên hiệu năng, tính bảo mật và trải nghiệm người dùng xuất sắc trên mọi thiết bị.

---

### 🛠️ Ngăn Xếp Công Nghệ (Tech Stack)

**Frontend:**
- HTML5 ngữ nghĩa kết hợp các thuộc tính ARIA nhằm đảm bảo khả năng tiếp cận (tuân thủ chuẩn WCAG 2.1 AA)
- CSS3 sử dụng CSS Custom Properties (biến CSS), Flexbox và CSS Grid
- Vanilla JavaScript (ES2022+) sử dụng kiến trúc mô-đun (ES Modules)
- Không dùng các framework nặng nề — giữ cho gói mã nguồn gọn nhẹ và tốc độ tải nhanh

**Backend API:**
- Thiết kế RESTful API tuân theo đặc tả OpenAPI 3.0
- Định dạng yêu cầu/phản hồi JSON đi kèm các mã trạng thái HTTP chuẩn xác
- Cấu hình CORS cho các yêu cầu liên nguồn (cross-origin requests)
- Giới hạn tần suất gửi yêu cầu (rate limiting) theo từng địa chỉ IP và theo từng API key

**Thư viện (qua CDN hoặc đóng gói kèm):**
- Tạo mã QR (ví dụ: `qrcode.js` hoặc `qr-creator`)
- Thư viện vẽ biểu đồ phục vụ phân tích (ví dụ: Chart.js hoặc giải pháp thay thế gọn nhẹ)
- Bộ chọn ngày tháng cho tính năng hết hạn liên kết (ví dụ: `flatpickr`)
- Hệ thống thông báo nổi (toast notification) (tự xây dựng, không dùng thư viện phụ thuộc)

---

### 🎨 Frontend — Yêu Cầu Giao Diện & Trải Nghiệm Người Dùng (UI/UX)

**Bố Cục & Thiết Kế:**
- Thiết kế tối giản, sạch sẽ với phần mở đầu (hero section) nổi bật bởi **ô nhập URL lớn, bắt mắt** nằm ngay chính giữa trang
- Triển khai **nút chuyển đổi chế độ tối/sáng (dark/light mode)** có khả năng tự nhận diện tùy chọn hệ thống (`prefers-color-scheme`) và lưu trạng thái vào `localStorage`
- Sử dụng hệ thống thiết kế nhất quán: thang tỷ lệ chữ (typography scale), các token khoảng cách, bảng màu chuẩn
- Các hiệu ứng chuyển cảnh và hoạt ảnh vi mô (micro-animations) mượt mà (hiện dần, trượt lên, gợn sóng trên nút bấm) cho mọi phần tử tương tác
- Trạng thái khung xương chờ tải (skeleton loading) cho các nội dung bất đồng bộ (bảng phân tích, bảng lịch sử)

**Thiết Kế Đáp Ứng (Responsive Design):**
- Tiếp cận theo hướng ưu tiên thiết bị di động (mobile-first) với các điểm ngắt (breakpoints) tại 480px, 768px, 1024px và 1440px
- Vùng chạm thân thiện với màn hình cảm ứng (kích thước tối thiểu 44×44px)
- Menu điều hướng có thể thu gọn trên thiết bị di động (menu hamburger kèm hiệu ứng chuyển động)
- Cỡ chữ linh hoạt sử dụng hàm `clamp()`

**Khả Năng Tiếp Cận (Accessibility):**
- Hỗ trợ điều hướng hoàn toàn bằng bàn phím (thứ tự phím Tab, giữ tiêu điểm focus bên trong cửa sổ modal)
- Thông báo qua trình đọc màn hình cho các nội dung thay đổi động (vùng `aria-live`)
- Đường viền chỉ báo tiêu điểm (focus indicators) hiển thị rõ ràng trên mọi phần tử tương tác
- Tỷ lệ tương phản màu sắc đạt chuẩn (tối thiểu 4.5:1 đối với văn bản)
- Hỗ trợ chế độ giảm chuyển động (`prefers-reduced-motion`)

---

### ⚙️ Các Tính Năng Cốt Lõi

#### 1. Bộ Máy Rút Ngắn URL
- Ô nhập liệu nổi bật có văn bản gợi ý (placeholder) và phản hồi kiểm tra tính hợp lệ theo thời gian thực
- **Kiểm tra tính hợp lệ của URL**: Kiểm tra giao thức hợp lệ (`http://`, `https://`), cấu trúc tên miền chuẩn xác, và từ chối các URL sai định dạng bằng cách kết hợp cả biểu thức chính quy (regex) lẫn API hàm tạo `URL`
- **Làm sạch URL (URL sanitization)**: Loại bỏ các tham số theo dõi (UTM, fbclid, gclid), xóa các dấu gạch chéo dư thừa, chuẩn hóa giao thức và chữ hoa/thường
- Tự động thêm tiền tố `https://` nếu người dùng không nhập giao thức
- Hiển thị ngay URL đã rút gọn sau khi tạo kèm theo hoạt ảnh báo thành công
- **Rút ngắn URL hàng loạt**: Cho phép người dùng dán nhiều URL cùng lúc (mỗi dòng một URL) và rút ngắn tất cả chỉ trong một lần thao tác

#### 2. Hỗ Trợ Bí Danh Tùy Chỉnh (Custom Alias)
- Trường nhập bí danh tùy chỉnh không bắt buộc (ví dụ: `short.ly/my-brand`)
- Kiểm tra tình trạng khả dụng theo thời gian thực ngay khi người dùng gõ phím (gọi API có debounce 300ms)
- Quy tắc kiểm tra bí danh: chỉ chấp nhận chữ cái, chữ số, dấu gạch ngang và dấu gạch dưới; độ dài từ 3–30 ký tự; danh sách đen chặn các từ khóa dành riêng cho hệ thống (ví dụ: `admin`, `api`, `login`, `static`)
- Thông báo lỗi rõ ràng nếu bí danh đã có người dùng hoặc không hợp lệ

#### 3. Tạo Mã QR
- Tự động tạo mã QR cho mọi URL rút gọn ngay khi khởi tạo xong
- Cửa sổ modal/khung điều khiển mã QR với các tùy chọn:
  - Tải xuống dưới định dạng PNG (độ phân giải cao, 1024×1024)
  - Tải xuống dưới định dạng SVG (đồ họa vector, phóng to không vỡ hình)
  - Tùy chỉnh màu sắc tiền cảnh/hậu cảnh
  - Chèn thêm lớp phủ logo nhỏ ở chính giữa (tùy chọn)
- Đảm bảo mã QR mã hóa đầy đủ URL rút gọn và có thể quét dễ dàng ngay cả ở kích thước nhỏ

#### 4. Theo Dõi Lượt Nhấp & Bảng Điều Khiển Phân Tích
- Ghi nhận mọi lượt chuyển hướng với các điểm dữ liệu sau:
  - Dấu thời gian (múi giờ UTC)
  - Quốc gia và thành phố (thông qua API định vị địa lý từ địa chỉ IP)
  - Loại thiết bị (điện thoại, máy tính bảng, máy tính để bàn)
  - Hệ điều hành và trình duyệt (thông qua phân tích chuỗi User-Agent)
  - URL nguồn giới thiệu (Referrer URL)
  - Khách truy cập duy nhất so với khách quay lại (thông qua cookie/fingerprint)
- **Bảng Điều Khiển Phân Tích** riêng cho từng liên kết bao gồm:
  - Bộ đếm tổng số lượt nhấp và số lượt nhấp duy nhất
  - Biểu đồ đường thể hiện số lượt nhấp theo thời gian (có bộ lọc: 24 giờ qua, 7 ngày, 30 ngày, 90 ngày, toàn thời gian)
  - Bản đồ phân bố địa lý hoặc biểu đồ cột ngang (10 quốc gia dẫn đầu)
  - Tỷ lệ phân bổ Thiết bị/Hệ điều hành/Trình duyệt (biểu đồ tròn hoặc biểu đồ vòng donut)
  - Bảng thống kê các nguồn giới thiệu (referrers) hàng đầu
  - Biểu đồ nhiệt (heatmap) lượt nhấp theo các ngày trong tuần và các giờ trong ngày
- Tổng quan phân tích gộp chung trên bảng điều khiển chính (tổng số liên kết, tổng số lượt nhấp, liên kết phổ biến nhất)

#### 5. Thời Hạn Liên Kết (Link Expiration)
- Bộ chọn ngày/giờ hết hạn không bắt buộc khi tạo liên kết
- Hỗ trợ các mốc cài đặt sẵn: 1 giờ, 24 giờ, 7 ngày, 30 ngày, hoặc chọn ngày tùy chỉnh
- Hiển thị đồng hồ đếm ngược hoặc huy hiệu "hết hạn sau X" trên các liên kết đang hoạt động
- Tự động vô hiệu hóa các liên kết đã quá hạn và hiển thị trang thông báo thân thiện "Liên kết này đã hết hạn" khi có người truy cập chuyển hướng
- Cho phép người dùng gia hạn thêm hoặc gỡ bỏ thời hạn trên các liên kết hiện có

#### 6. Bảo Vệ Bằng Mật Khẩu
- Nút bật/tắt mật khẩu không bắt buộc khi tạo liên kết
- Nếu được bật, hiển thị trang cổng nhập mật khẩu trước khi chuyển hướng tới URL đích
- Băm (hash) mật khẩu ở phía máy chủ bằng thuật toán bcrypt hoặc Argon2 (tuyệt đối không lưu mật khẩu dạng văn bản thuần)
- Giới hạn số lần thử mật khẩu (tối đa 5 lần mỗi phút trên mỗi IP) kèm thông báo khóa tạm thời
- Chỉ báo trực quan (biểu tượng ổ khóa 🔒) trên các liên kết được bảo vệ bằng mật khẩu trong danh sách lịch sử

#### 7. Sao Chép Vào Bộ Nhớ Tạm (Copy-to-Clipboard)
- Nút sao chép chỉ với một cú nhấp chuột nằm ngay cạnh mỗi URL rút gọn
- Sử dụng Clipboard API (`navigator.clipboard.writeText()`) kèm phương án dự phòng dùng `document.execCommand('copy')`
- Xác nhận trực quan: biểu tượng trên nút chuyển thành dấu tích ✅ cùng chú giải/thông báo nổi "Đã sao chép!" tự động biến mất sau 2 giây
- Hỗ trợ phím tắt bàn phím (ví dụ: nhấn `Ctrl+Shift+C` để sao chép liên kết vừa tạo gần nhất)

#### 8. Lịch Sử & Quản Lý URL
- Bảng/lưới lịch sử lưu trữ bền vững toàn bộ các URL đã rút gọn (lưu trong `localStorage` đối với khách vãng lai, lưu trong cơ sở dữ liệu đối với người dùng đã đăng nhập)
- Mỗi mục hiển thị: URL gốc (được cắt gọn kèm chú giải tooltip khi di chuột), URL rút gọn, ngày tạo, số lượt nhấp, trạng thái (đang hoạt động/đã hết hạn/có mật khẩu), và các nút thao tác
- **Tìm kiếm**: Tìm kiếm toàn văn trên URL gốc, bí danh và thẻ phân loại (tags)
- **Bộ lọc**: Lọc theo trạng thái (đang hoạt động, đã hết hạn, có bảo vệ), khoảng thời gian, khoảng số lượt nhấp, và thẻ phân loại
- **Sắp xếp**: Theo ngày tạo, số lượt nhấp, thứ tự bảng chữ cái (tăng dần/giảm dần)
- **Phân trang hoặc cuộn vô tận** đối với danh sách lịch sử lớn (25 mục mỗi trang)
- **Thao tác hàng loạt**: Chọn nhiều liên kết cùng lúc để xóa, vô hiệu hóa, hoặc xuất ra tệp CSV/JSON
- **Thẻ/Nhãn phân loại (Tags/Labels)**: Cho phép người dùng gắn các thẻ tùy chỉnh để sắp xếp liên kết (ví dụ: "marketing", "social", "campaign-q4")

---

### 🔒 Yêu Cầu Bảo Mật
- Làm sạch dữ liệu đầu vào ở cả phía máy khách và máy chủ để ngăn chặn tấn công XSS
- Bảo vệ chống tấn công CSRF trên tất cả các điểm cuối (endpoints) API có làm thay đổi trạng thái
- Kiểm tra tính hợp lệ và làm sạch địa chỉ đích chuyển hướng nhằm ngăn chặn lỗ hổng chuyển hướng mở (open redirect) (chỉ đưa các giao thức hợp lệ vào danh sách trắng, chặn các lược đồ `javascript:`, `data:`, `file:`)
- Triển khai các tiêu đề Content Security Policy (CSP)
- Giới hạn tần suất (Rate limiting): tối đa 20 lần tạo liên kết mỗi phút trên mỗi IP; tối đa 100 yêu cầu API mỗi phút
- Các tiêu đề HTTP bảo mật: `X-Content-Type-Options`, `X-Frame-Options`, `Strict-Transport-Security`
- Tuyệt đối không để lộ ID nội bộ; sử dụng nanoid hoặc UUID làm mã định danh liên kết công khai

---

### ⚡ Yêu Cầu Hiệu Năng
- Điểm số Lighthouse mục tiêu: Performance >90, Accessibility >95, Best Practices >95, SEO >90
- Tải trễ (lazy-load) các biểu đồ phân tích và những tài nguyên không thiết yếu
- Thực hiện debounce cho toàn bộ các ô nhập liệu tìm kiếm và kiểm tra tính hợp lệ
- Sử dụng `IntersectionObserver` cho các hoạt ảnh kích hoạt theo thao tác cuộn trang và tải trễ (lazy loading)
- Giảm thiểu tối đa việc tính toán lại bố cục (reflows) và vẽ lại (repaints); sử dụng thuộc tính CSS `transform` và `opacity` cho các hiệu ứng chuyển động
- Lưu bộ nhớ đệm (cache) các phản hồi API ở những nơi phù hợp (ví dụ: dữ liệu phân tích với thời gian sống TTL là 60 giây)
- Các thao tác chuyển hướng phải được xử lý xong trong vòng chưa đầy 100ms ở phía máy chủ

---

### 🧪 Xử Lý Lỗi & Các Trường Hợp Biên (Edge Cases)
- Hiển thị thông báo lỗi thân thiện, mượt mà đối với các trường hợp: mất kết nối mạng, hết thời gian chờ phản hồi API (timeouts), URL không hợp lệ, trùng lặp bí danh, lỗi máy chủ (500), vượt quá giới hạn tần suất yêu cầu (429)
- Phát hiện trạng thái ngoại tuyến (offline): hiển thị thanh thông báo khi người dùng mất kết nối mạng và đưa các thao tác vào hàng đợi để thử lại sau
- Xử lý tốt các URL cực dài (lên tới 2048 ký tự) mà không làm vỡ bố cục giao diện
- Xử lý chính xác các tên miền quốc tế hóa/chứa ký tự Unicode (IDN)
- Có hình minh họa trạng thái trống (empty state) khi người dùng chưa có liên kết nào hoặc chưa có dữ liệu phân tích

---

### 📁 Sản Phẩm Bàn Giao (Deliverables)
1. Mã nguồn hoàn chỉnh, có chú thích rõ ràng cùng cấu trúc thư mục mạch lạc
2. Tệp `README.md` kèm hướng dẫn cài đặt, tài liệu API và danh sách tính năng
3. Bảng tra cứu các điểm cuối API (phương thức, đường dẫn, tham số, định dạng phản hồi)
4. Chế độ `demo` được nạp sẵn dữ liệu mẫu để có thể xem trước ngay lập tức
5. Hiển thị đáp ứng tốt trên các trình duyệt Chrome, Firefox, Safari và Edge (2 phiên bản mới nhất)

---

> **Lưu ý:** Hãy xây dựng ứng dụng này theo từng bước tăng dần — bắt đầu với luồng rút ngắn cốt lõi (nhập liệu → kiểm tra hợp lệ → rút ngắn → hiển thị → sao chép), sau đó mới bổ sung dần các lớp tính năng mã QR, phân tích, bí danh tùy chỉnh, thời hạn liên kết, bảo vệ bằng mật khẩu và bảng điều khiển lịch sử đầy đủ. Đảm bảo mỗi tính năng đều hoạt động hoàn chỉnh và được kiểm thử kỹ trước khi chuyển sang tính năng tiếp theo.
Web DevelopmentSecurityJavaScript+5
A@alejandrogarciagaray
0
Trình Viết Danh sách Kiểm tra Đánh giá Khả năng Tiếp cận (Accessibility) cho Giao diện Web
Văn bản

Tạo danh sách kiểm tra đánh giá khả năng tiếp cận (accessibility audit checklist) theo định hướng WCAG được sắp xếp theo mức độ ưu tiên dưới định dạng YAML cho một giao diện hoặc luồng web cụ thể, bao gồm mức độ nghiêm trọng, cách kiểm thử và hướng khắc phục — thay vì liệt kê tràn lan mọi tiêu chí thành công một cách chung chung.

1Bạn là một chuyên gia về khả năng tiếp cận (accessibility) đang viết một danh sách kiểm tra đánh giá **có mục tiêu trọng tâm** cho một giao diện web. Bạn thiết kế riêng các mục kiểm tra sao cho sát với bề mặt sản phẩm được mô tả (biểu mẫu, bảng điều khiển, trang tiếp thị, v.v.) thay vì đổ ra toàn bộ mọi tiêu chí WCAG.
2
3## Đầu vào
4Người dùng mô tả một trang web, luồng thao tác hoặc thành phần giao diện (URL tùy chọn, ảnh chụp màn hình/mã HTML tùy chọn). Nếu bề mặt giao diện chưa rõ ràng, hãy hỏi tối đa 3 câu hỏi, sau đó tiến hành lập danh sách dựa trên các giả định được nêu rõ.
5
6## Đầu ra
7Chỉ phản hồi bằng **mã YAML duy nhất** (không dùng dấu rào markdown) theo cấu trúc sau:
8
9```yaml
10meta:
...+49 dòng nữa
Web DevelopmentBest PracticesFrontend+2
F@f
0
Next.js App Router taste
Văn bản

Các quy ước mang tính định hướng rõ ràng để xây dựng ứng dụng Next.js thực tế với App Router.

# Next.js App Router taste
- Mặc định sử dụng Server Components và chỉ thêm "use client" ở các component nhánh cuối (leaf components) cần tính tương tác. Độ tin cậy: 0.95
- Truy xuất dữ liệu trực tiếp trong Server Components thay vì dùng các API route cho việc đọc dữ liệu nội bộ. Độ tin cậy: 0.90
- Sử dụng Server Actions cho các thao tác biến đổi dữ liệu (mutations) và xác thực đầu vào bằng zod. Độ tin cậy: 0.85
- Đặt các component, bộ tải dữ liệu (loaders) và tệp kiểm thử cùng chỗ theo từng tính năng thay vì phân chia theo loại tệp. Độ tin cậy: 0.80
- Sử dụng Tailwind CSS với một tập hợp nhỏ các design token được khai báo trong biến CSS. Độ tin cậy: 0.85
- Sử dụng next/image và next/font cho toàn bộ hình ảnh và phông chữ. Độ tin cậy: 0.90
- Thêm loading.tsx và error.tsx vào mọi phân đoạn đường dẫn (route segment) có truy xuất dữ liệu. Độ tin cậy: 0.85
- Sử dụng Prisma hoặc Drizzle với các truy vấn được định kiểu (typed queries) và giữ trình khách (client) trong một tệp lib/db.ts duy nhất. Độ tin cậy: 0.80
- Lưu bộ nhớ đệm (cache) bằng thẻ fetch (fetch tags) và revalidateTag thay vì tự tạo bộ nhớ đệm thủ công trong bộ nhớ (in-memory). Độ tin cậy: 0.75
- Luôn xác thực các biến môi trường ngay khi khởi động bằng một mô-đun env được định kiểu. Độ tin cậy: 0.85
- Tạo siêu dữ liệu bằng hàm generateMetadata và luôn thiết lập hình ảnh Open Graph. Độ tin cậy: 0.85
- Tránh sử dụng các tệp gom (barrel files) trong những ứng dụng lớn vì chúng làm chậm thời gian biên dịch (build). Độ tin cậy: 0.70
- Chỉ sử dụng middleware cho việc chuyển hướng xác thực (auth redirects) và định tuyến ngôn ngữ/khu vực (locale routing), tuyệt đối không đặt logic nặng ở đây. Độ tin cậy: 0.80
- Kiểm thử các luồng quan trọng bằng Playwright và kiểm thử các component bằng vitest. Độ tin cậy: 0.80
- Triển khai trên Vercel kèm theo bản triển khai xem trước (preview deployments) cho mỗi pull request. Độ tin cậy: 0.75
Web DevelopmentFrontendReact+1
F@f
0
Công cụ Kiểm tra Lệch chuẩn Đặc tả OpenAPI
Kỹ năng

So sánh đặc tả OpenAPI YAML/JSON với danh sách các route đã được triển khai và gắn cờ cảnh báo sự lệch chuẩn (drift): thiếu thao tác (operation), thừa route, lệch phương thức (method), và khác biệt về cấu trúc tham số đường dẫn (path parameter). Sử dụng khi đánh giá PR của API, trước khi phát hành, hoặc khi hỏi "mã nguồn của chúng ta có còn khớp với OpenAPI không?". Bao gồm công cụ kiểm tra bằng thư viện chuẩn Python có hỗ trợ --self-test.

---
name: openapi-spec-drift-checker
description: So sánh đặc tả OpenAPI YAML/JSON với danh sách các route đã được triển khai và gắn cờ cảnh báo sự lệch chuẩn (drift): thiếu thao tác (operation), thừa route, lệch phương thức (method), và khác biệt về cấu trúc tham số đường dẫn (path parameter). Sử dụng khi đánh giá PR của API, trước khi phát hành, hoặc khi hỏi "mã nguồn của chúng ta có còn khớp với OpenAPI không?". Bao gồm công cụ kiểm tra bằng thư viện chuẩn Python có hỗ trợ --self-test.
---

# Công cụ Kiểm tra Lệch chuẩn Đặc tả OpenAPI

Bạn giúp các đội ngũ phát triển API tìm ra sự lệch chuẩn (drift) giữa tài liệu OpenAPI 3.x và các route mà mã nguồn của họ thực sự phục vụ. Bạn so sánh các đường dẫn (path) và phương thức (method), gắn cờ các thao tác bị thiếu hoặc thừa, đồng thời chỉ ra những điểm không khớp về tham số hoặc phản hồi làm ảnh hưởng đến phía client. Bạn không tự bịa ra các endpoint; bạn chỉ báo cáo những gì dữ liệu đầu vào thể hiện.

## Các tệp trong kỹ năng này

- `scripts/check_drift.py` - so sánh một đoạn đặc tả OpenAPI nhỏ với danh sách route và in ra báo cáo lệch chuẩn (chỉ dùng thư viện chuẩn Python 3)
- `references/openapi-checklist.md` - những điều cần xác minh trong đặc tả trước khi so sánh
- `references/common-drift.md` - các nguyên nhân gây lệch chuẩn thường gặp và cách khắc phục
- `templates/drift-report.md` - cấu trúc báo cáo dùng cho các PR và ghi chú phát hành (release notes)
- `examples/openapi-snippet.yaml` - đoạn mẫu OpenAPI dùng cho bản chạy thử (demo)

## Quy trình làm việc

### 1. Thu thập dữ liệu đầu vào
Yêu cầu cung cấp, hoặc suy luận từ ngữ cảnh:
- Tệp OpenAPI (YAML hoặc JSON) hoặc phần paths được dán trực tiếp
- Các route đã triển khai: danh sách phương thức + đường dẫn từ bộ định tuyến (router) của framework, được trích xuất từ mã nguồn, hoặc từ cấu hình gateway
- Liệu tên tham số đường dẫn có bắt buộc phải khớp chính xác từng chữ hay không (`{id}` so với `{userId}`)

### 2. Chuẩn hóa dữ liệu
Chuyển các phương thức về chữ thường. Cắt bỏ dấu gạch chéo ở cuối đường dẫn (trailing slash). Coi `/users/{id}` và `/users/{userId}` có cùng hình thái đường dẫn trừ khi nhóm phát triển yêu cầu khớp chính xác tên tham số.

### 3. Chạy công cụ kiểm tra
```
python3 scripts/check_drift.py --spec examples/openapi-snippet.yaml --routes examples/routes.txt
python3 scripts/check_drift.py --self-test
```
Nếu danh sách các route chưa có sẵn trong tệp, hãy giúp người dùng trích xuất chúng từ mã nguồn trước.

### 4. Phân loại các phát hiện
- **missing_in_code**: có trong OpenAPI, nhưng chưa được triển khai trong mã nguồn
- **missing_in_spec**: đã triển khai trong mã nguồn, nhưng không có trong OpenAPI
- **method_mismatch**: đường dẫn có tồn tại nhưng khác phương thức HTTP (verb)
- **param_shape_diff**: cùng mẫu đường dẫn, nhưng khác tên tham số (chế độ nghiêm ngặt tùy chọn)

### 5. Trả lời
Trả về bảng tóm tắt số lượng ngắn gọn, sau đó liệt kê từng phát hiện kèm mức độ nghiêm trọng (high/medium/low) và gợi ý cách khắc phục. Sử dụng `templates/drift-report.md` để viết bình luận vào PR.

## Quy tắc

- Ưu tiên cập nhật lại tài liệu theo thực tế khi mã nguồn được viết có chủ đích và bản đặc tả đã lỗi thời; ưu tiên viết bổ sung mã nguồn khi bản đặc tả là hợp đồng giao tiếp đã được công bố cho các client.
- Không bao giờ tuyên bố rằng đã thực hiện đánh giá bảo mật; chỉ báo cáo sự lệch chuẩn về mặt cấu trúc.
- Nếu không thể phân tích cú pháp (parse) tệp OpenAPI, hãy dừng lại và hiển thị lỗi phân tích cú pháp đó.
FILE:references/openapi-checklist.md
# Danh sách kiểm tra OpenAPI trước khi đối chiếu

Trước khi chạy kiểm tra lệch chuẩn, hãy xác nhận bản đặc tả có tính nhất quán:

1. Trường **openapi** là `3.0.x` hoặc `3.1.x`.
2. Phần **paths** có tồn tại và bắt đầu bằng dấu `/`.
3. Mọi thao tác (operation) đều có phương thức HTTP (`get`, `post`, `put`, `patch`, `delete`, `head`, `options`).
4. Các tham số đường dẫn được khai báo trong đường dẫn (`{id}`) cũng xuất hiện dưới mục `parameters` với `in: path` và `required: true`.
5. Các đích tham chiếu `$ref` đều phân giải được (hoặc bạn đang so sánh một tài liệu đã được phân giải/đóng gói hoàn chỉnh).
6. Đã nắm rõ đường dẫn cơ sở (base path) trong URL của servers: nếu `servers[].url` bao gồm `/api/v1`, các route có thể được liệt kê kèm hoặc không kèm tiền tố đó—hãy chọn một quy ước thống nhất và giữ nguyên trong suốt quá trình so sánh.
7. Các thao tác không còn khuyên dùng (deprecated) vẫn phải được liệt kê cho đến khi bị xóa hoàn toàn khỏi mã nguồn; hãy đánh dấu chúng trong báo cáo thay vì bỏ qua.

## Cấu trúc paths tối thiểu

```yaml
paths:
  /pets:
    get:
      summary: List pets
    post:
      summary: Create pet
  /pets/{petId}:
    get:
      parameters:
        - name: petId
          in: path
          required: true
          schema: { type: string }
```
FILE:references/common-drift.md
# Các trường hợp lệch chuẩn OpenAPI thường gặp

| Triệu chứng | Nguyên nhân có thể | Cách khắc phục |
| --- | --- | --- |
| Đặc tả có `GET /users`, mã nguồn không có | Đặc tả được viết trước khi triển khai mã nguồn | Triển khai mã nguồn hoặc xóa khỏi bản đặc tả đã công bố |
| Mã nguồn có `POST /users/{id}/reset`, thiếu trong đặc tả | Bản vá nóng (hotfix) được đưa lên mà không cập nhật tài liệu | Thêm thao tác vào đặc tả; tăng phiên bản phụ (minor) nếu là API công khai |
| Đặc tả là `PUT`, mã nguồn là `PATCH` | Ngữ nghĩa cập nhật một phần đã thay đổi | Đồng bộ lại phương thức; lưu ý thay đổi gây lỗi tương thích (breaking change) nếu client đang gọi PUT |
| `{userId}` so với `{id}` | Framework tự động đặt tên so với đặc tả viết tay | Chuẩn hóa khi so sánh; ghi tài liệu quy tắc đặt tên |
| Chỉ có dấu gạch chéo cuối trong mã nguồn | Bộ định tuyến (router) chấp nhận linh hoạt dấu gạch chéo | Cắt bỏ khi so sánh; chọn dạng chuẩn tắc (canonical) trong đặc tả |
| Đường dẫn cơ sở `/api` chỉ có ở gateway | Mục servers trong đặc tả bỏ qua tiền tố của gateway | Cắt bỏ hoặc thêm cùng một tiền tố ở cả hai phía |
| Webhooks / callbacks chỉ có trong đặc tả | Không phải là các route HTTP thông thường | Loại trừ khỏi việc so sánh route |
| Route tải tệp lên chưa được ghi tài liệu | Bổ sung multipart vào giai đoạn muộn | Ghi tài liệu `multipart/form-data` vào requestBody |

## Hướng dẫn đánh giá mức độ nghiêm trọng

- **High**: các client công khai đã và đang gọi thao tác này; thiếu phương thức hoặc đường dẫn ở một trong hai phía.
- **Medium**: route chỉ dùng nội bộ bị thiếu trong đặc tả; đổi tên tham số nhưng có bí danh (alias).
- **Low**: dấu gạch chéo ở cuối, nhiễu từ các phương thức HEAD/OPTIONS tùy chọn, đã đánh dấu deprecated nhưng vẫn còn tồn tại.
FILE:templates/drift-report.md
# Báo cáo lệch chuẩn OpenAPI

- **Đặc tả (Spec)**: <path or URL>
- **Nguồn trích xuất route**: <router dump / gateway / manual>
- **Thời điểm so sánh**: <ISO timestamp>
- **Tóm tắt**: missing_in_code=<n>, missing_in_spec=<n>, method_mismatch=<n>, param_shape_diff=<n>

## Các phát hiện

| Mức độ nghiêm trọng | Loại | Đặc tả (Spec) | Mã nguồn (Code) | Ghi chú |
| --- | --- | --- | --- | --- |
| high | missing_in_code | `GET /pets/{petId}` | — | Triển khai hoặc gỡ khỏi tài liệu công bố |
| medium | missing_in_spec | — | `POST /pets/{petId}/adopt` | Bổ sung vào tài liệu trước khi phát hành |

## Quyết định

- [ ] Cập nhật OpenAPI
- [ ] Cập nhật mã nguồn
- [ ] Chấp nhận như một ngoại lệ đã biết (nêu rõ lý do)

## Các việc cần theo dõi tiếp

- [ ]
FILE:examples/openapi-snippet.yaml
openapi: 3.0.3
info:
  title: Pets demo
  version: 0.1.0
paths:
  /pets:
    get:
      summary: List pets
      operationId: listPets
    post:
      summary: Create pet
      operationId: createPet
  /pets/{petId}:
    get:
      summary: Get pet by id
      operationId: getPet
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: string
    delete:
      summary: Delete pet
      operationId: deletePet
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: string
  /health:
    get:
      summary: Health check
      operationId: health
FILE:scripts/check_drift.py
#!/usr/bin/env python3
"""Compare OpenAPI paths to an implemented route list.

Usage:
    python3 check_drift.py --spec openapi.yaml --routes routes.txt
    python3 check_drift.py --spec openapi.yaml --route 'GET /pets' --route 'POST /pets'
    python3 check_drift.py --self-test

Route file format: one "METHOD /path" per line. Comments (#) and blank lines ignored.
Python 3 standard library only.
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from typing import Dict, List, Set, Tuple

try:
    import yaml  # type: ignore
except ImportError:
    yaml = None

PathKey = str  # normalized path template
Method = str


def normalize_path(path: str) -> str:
    path = path.strip()
    if not path.startswith("/"):
        path = "/" + path
    if len(path) > 1 and path.endswith("/"):
        path = path.rstrip("/")
    # unify parameter names to {}
    return re.sub(r"\{[^}]+\}", "{}", path)


def normalize_method(m: str) -> str:
    return m.strip().upper()


def parse_routes(lines: List[str]) -> Dict[PathKey, Set[Method]]:
    out: Dict[PathKey, Set[Method]] = {}
    for raw in lines:
        line = raw.split("#", 1)[0].strip()
        if not line:
            continue
        parts = line.split()
        if len(parts) < 2:
            raise ValueError(f"Bad route line: {raw!r}")
        method, path = normalize_method(parts[0]), normalize_path(parts[1])
        out.setdefault(path, set()).add(method)
    return out


def _parse_openapi_json(text: str) -> dict:
    return json.loads(text)


def _parse_openapi_yaml_simple(text: str) -> dict:
    """Minimal YAML subset parser for simple OpenAPI path maps when PyYAML is absent.

    Supports the examples shipped with this skill (no anchors, no multiline).
    Prefer PyYAML when installed.
    """
    # Very small fallback: extract path keys and methods via regex for our demos.
    paths: Dict[str, dict] = {}
    current_path = None
    for raw in text.splitlines():
        if raw.startswith("  /") and raw.rstrip().endswith(":"):
            current_path = raw.strip()[:-1]
            paths[current_path] = {}
            continue
        if current_path and re.match(r"^    (get|post|put|patch|delete|head|options):", raw):
            method = raw.strip().split(":", 1)[0]
            paths[current_path][method] = {}
    if not paths:
        raise ValueError("Could not parse OpenAPI YAML without PyYAML; install pyyaml or pass JSON.")
    return {"paths": paths}


def load_spec(path: str) -> Dict[PathKey, Set[Method]]:
    text = open(path, encoding="utf-8").read()
    if path.endswith(".json"):
        data = _parse_openapi_json(text)
    else:
        if yaml is not None:
            data = yaml.safe_load(text)
        else:
            # try JSON first, then simple YAML
            try:
                data = _parse_openapi_json(text)
            except json.JSONDecodeError:
                data = _parse_openapi_yaml_simple(text)
    paths = data.get("paths") or {}
    out: Dict[PathKey, Set[Method]] = {}
    http = {"get", "post", "put", "patch", "delete", "head", "options", "trace"}
    for p, item in paths.items():
        if not isinstance(item, dict):
            continue
        methods = {normalize_method(m) for m in item if m.lower() in http}
        if methods:
            out[normalize_path(p)] = methods
    return out


def compare(spec: Dict[PathKey, Set[Method]], code: Dict[PathKey, Set[Method]]) -> List[dict]:
    findings = []
    all_paths = set(spec) | set(code)
    for p in sorted(all_paths):
        s = spec.get(p, set())
        c = code.get(p, set())
        if s and not c:
            for m in sorted(s):
                findings.append({"kind": "missing_in_code", "severity": "high", "method": m, "path": p})
        elif c and not s:
            for m in sorted(c):
                findings.append({"kind": "missing_in_spec", "severity": "medium", "method": m, "path": p})
        else:
            only_spec = s - c
            only_code = c - s
            for m in sorted(only_spec):
                findings.append({"kind": "method_mismatch", "severity": "high", "method": m, "path": p, "side": "spec_only"})
            for m in sorted(only_code):
                findings.append({"kind": "method_mismatch", "severity": "high", "method": m, "path": p, "side": "code_only"})
    return findings


def format_report(findings: List[dict]) -> str:
    counts: Dict[str, int] = {}
    for f in findings:
        counts[f["kind"]] = counts.get(f["kind"], 0) + 1
    lines = ["Drift summary: " + ", ".join(f"{k}={v}" for k, v in sorted(counts.items()))]
    if not findings:
        lines.append("No drift found.")
        return "\n".join(lines)
    for f in findings:
        side = f.get("side", "")
        extra = f" ({side})" if side else ""
        lines.append(f"- [{f['severity']}] {f['kind']}: {f['method']} {f['path']}{extra}")
    return "\n".join(lines)


def self_test() -> int:
    spec = {
        normalize_path("/pets"): {"GET", "POST"},
        normalize_path("/pets/{petId}"): {"GET", "DELETE"},
        normalize_path("/health"): {"GET"},
    }
    code = {
        normalize_path("/pets"): {"GET", "POST"},
        normalize_path("/pets/{id}"): {"GET"},  # same shape, missing DELETE
        normalize_path("/pets/{id}/adopt"): {"POST"},  # extra
    }
    findings = compare(spec, code)
    kinds = sorted(f["kind"] + ":" + f["method"] + ":" + f["path"] for f in findings)

    cases = [
        ("missing delete on pet", any(f["kind"] == "method_mismatch" and f["method"] == "DELETE" for f in findings), True),
        ("extra adopt route", any(f["kind"] == "missing_in_spec" and f["path"].endswith("/adopt") for f in findings), True),
        ("health missing in code", any(f["kind"] == "missing_in_code" and f["path"] == "/health" for f in findings), True),
        ("list pets matched", not any(f["path"] == "/pets" and f["kind"] == "missing_in_code" for f in findings), True),
        ("normalize params", normalize_path("/pets/{petId}") == normalize_path("/pets/{id}"), True),
        ("normalize slash", normalize_path("/pets/") == "/pets", True),
        ("parse routes", parse_routes(["GET /pets", "POST /pets # create"]) == {"/pets": {"GET", "POST"}}, True),
        ("empty compare", compare({"/x": {"GET"}}, {"/x": {"GET"}}) == [], True),
    ]

    # parse simple yaml fallback
    sample = """openapi: 3.0.3\npaths:\n  /pets:\n    get:\n      summary: x\n    post:\n      summary: y\n"""
    parsed = _parse_openapi_yaml_simple(sample)
    cases.append(("yaml fallback paths", "get" in parsed["paths"]["/pets"] and "post" in parsed["paths"]["/pets"], True))

    failed = 0
    for name, got, expected in cases:
        ok = got == expected
        failed += not ok
        print(f"{'PASS' if ok else 'FAIL'}  {name}: got={got!r} expected={expected!r}")
    # also assert finding count sanity
    ok = len(findings) >= 3
    failed += not ok
    print(f"{'PASS' if ok else 'FAIL'}  findings_count>={3}: got={len(findings)}")
    print(f"\n{len(cases)+1 - failed}/{len(cases)+1} passed")
    return 1 if failed else 0


def main(argv=None) -> int:
    p = argparse.ArgumentParser(description="Compare OpenAPI paths to implemented routes.")
    p.add_argument("--spec", help="OpenAPI YAML or JSON file")
    p.add_argument("--routes", help="File with METHOD /path lines")
    p.add_argument("--route", action="append", default=[], help="Single METHOD /path (repeatable)")
    p.add_argument("--json", action="store_true", help="Print findings as JSON")
    p.add_argument("--self-test", action="store_true")
    a = p.parse_args(argv)
    if a.self_test:
        return self_test()
    if not a.spec:
        p.error("--spec is required (or use --self-test)")
    route_lines: List[str] = list(a.route)
    if a.routes:
        route_lines.extend(open(a.routes, encoding="utf-8").read().splitlines())
    if not route_lines:
        p.error("provide --routes and/or --route")
    spec = load_spec(a.spec)
    code = parse_routes(route_lines)
    findings = compare(spec, code)
    if a.json:
        print(json.dumps(findings, indent=2))
    else:
        print(format_report(findings))
    return 0 if not findings else 2


if __name__ == "__main__":
    sys.exit(main())
BackendWeb DevelopmentBest Practices+3
F@f
0
Phong cách CSS taste
Văn bản

Các ưu tiên mang tính định hướng rõ ràng để viết CSS dễ bảo trì, hỗ trợ khả năng tiếp cận (accessible) và đáp ứng (responsive) trong các ứng dụng web hiện đại.

# Phong cách (taste) CSS của một lập trình viên frontend có tư duy thiết kế
- Sử dụng các lớp tiện ích (utility classes) của Tailwind CSS để định kiểu cho component trong mã nguồn ứng dụng. Độ tin cậy: 0.80
- Định nghĩa màu sắc, khoảng cách (spacing), độ bo góc (radii) và phông chữ dưới dạng các design token trong thuộc tính tùy chỉnh CSS (CSS custom properties). Độ tin cậy: 0.90
- Không bao giờ gán cứng (hardcode) mã màu hex bên trong các component; luôn tham chiếu đến một token. Độ tin cậy: 0.85
- Xây dựng bố cục bằng flexbox và grid, sử dụng thuộc tính gap để tạo khoảng cách thay vì dùng margin giữa các phần tử anh em (siblings). Độ tin cậy: 0.90
- Thiết kế ưu tiên thiết bị di động (mobile first) và thêm các điểm ngắt (breakpoints) min-width cho màn hình lớn hơn. Độ tin cậy: 0.85
- Sử dụng container queries cho các component nằm trong những vùng chứa có chiều rộng khác nhau. Độ tin cậy: 0.75
- Sử dụng đơn vị rem cho kích thước phông chữ và khoảng cách, và chỉ dùng px cho đường viền mảnh (hairline borders). Độ tin cậy: 0.80
- Sử dụng hàm clamp() cho kiểu chữ linh hoạt (fluid typography) thay vì ghi đè tại nhiều điểm ngắt. Độ tin cậy: 0.75
- Giữ độ ưu tiên (specificity) phẳng: bộ chọn một lớp duy nhất, không dùng ID và không dùng !important bên ngoài các lớp tiện ích. Độ tin cậy: 0.90
- Sử dụng cascade layers để sắp xếp thứ tự cho reset, base, components và utilities. Độ tin cậy: 0.70
- Hỗ trợ chế độ tối (dark mode) bằng prefers-color-scheme kết hợp với các token, thay vì nhân bản các kiểu dáng của component. Độ tin cậy: 0.85
- Tôn trọng prefers-reduced-motion và tắt các hiệu ứng chuyển động không cần thiết khi tùy chọn này được bật. Độ tin cậy: 0.90
- Không bao giờ xóa đường viền tiêu điểm (focus outlines); thay vào đó hãy định kiểu cho chúng bằng :focus-visible. Độ tin cậy: 0.95
- Đạt chuẩn độ tương phản WCAG AA cho tất cả văn bản và các phần tử tương tác. Độ tin cậy: 0.90
- Chỉ tạo hiệu ứng chuyển động (animate) trên thuộc tính transform và opacity để có các chuyển cảnh mượt mà và ít tốn tài nguyên. Độ tin cậy: 0.85
- Sử dụng các thuộc tính logic như margin-inline để bố cục hoạt động tốt với cả các ngôn ngữ viết từ phải sang trái (RTL). Độ tin cậy: 0.75
- Sử dụng bộ phông chữ hệ thống (system font stack) hoặc phông chữ biến thiên (variable fonts) tự lưu trữ với thuộc tính font-display: swap. Độ tin cậy: 0.80
- Đặt thuộc tính aspect-ratio cùng với width và height trên các phần tử đa phương tiện (media) để ngăn chặn hiện tượng dịch chuyển bố cục (layout shift). Độ tin cậy: 0.85
- Sử dụng tính năng lồng nhau (nesting) nguyên bản của CSS một cách tiết chế và không bao giờ vượt quá hai cấp độ. Độ tin cậy: 0.70
- Tự động sắp xếp các lớp Tailwind bằng plugin Prettier. Độ tin cậy: 0.80
- Tách thành một component riêng khi cùng một danh sách dài các lớp lặp đi lặp lại, thay vì tạo lớp bằng @apply. Độ tin cậy: 0.75
- Đảm bảo không có phần tử nào bị cuộn ngang ở chiều rộng khung nhìn (viewport) 320px. Độ tin cậy: 0.85
Web DevelopmentFrontendAccessibility+2
F@f
0
Bảo mật Web taste
Văn bản

Các thiết lập bảo mật mặc định mang tính định hướng rõ ràng cho việc xác thực, xử lý dữ liệu đầu vào và gia cố bảo mật HTTP trong các ứng dụng web.

# Gu bảo mật web của một lập trình viên full-stack
- Sử dụng cơ chế xác thực có sẵn của framework hoặc một thư viện đã được kiểm chứng kỹ lưỡng; không bao giờ tự viết thủ công phần lưu trữ mật khẩu hay quản lý phiên đăng nhập (session). Độ tin cậy: 0.95
- Băm (hash) mật khẩu bằng Argon2id hoặc bcrypt với hệ số chi phí tính toán (work factor) hợp lý. Độ tin cậy: 0.95
- Lưu trữ phiên làm việc (session) trong cookie có bật cờ HttpOnly, Secure, SameSite=Lax thay vì lưu trong localStorage. Độ tin cậy: 0.90
- Xoay vòng (đổi mới) mã định danh phiên (session id) khi đăng nhập và khi có sự thay đổi về quyền hạn. Độ tin cậy: 0.85
- Cung cấp passkey hoặc TOTP làm yếu tố xác thực thứ hai, và bắt buộc áp dụng đối với các tài khoản quản trị viên (admin). Độ tin cậy: 0.85
- Kiểm tra phân quyền (authorization) ở phía máy chủ cho mọi yêu cầu (request), bao gồm cả việc kiểm tra trên các mã định danh đối tượng (object ids), để ngăn chặn lỗ hổng IDOR. Độ tin cậy: 0.95
- Mặc định từ chối (Deny by default): các route mới tạo đều ở trạng thái riêng tư cho đến khi có người chủ động cấu hình công khai chúng. Độ tin cậy: 0.85
- Sử dụng truy vấn tham số hóa (parameterized queries) hoặc ORM ở mọi nơi; không bao giờ tạo câu lệnh SQL bằng cách nối chuỗi. Độ tin cậy: 0.95
- Dựa vào cơ chế tự động escape của template engine và coi mọi cơ chế chèn HTML thô (raw HTML) là một cờ cảnh báo cần rà soát kỹ khi duyệt mã nguồn. Độ tin cậy: 0.90
- Triển khai chính sách Content Security Policy (CSP) nghiêm ngặt sử dụng mã nonce, bắt đầu ở chế độ chỉ báo cáo (report-only mode). Độ tin cậy: 0.80
- Gửi kèm các header HSTS, X-Content-Type-Options: nosniff và frame-ancestors trên mọi phản hồi (response). Độ tin cậy: 0.85
- Bảo vệ các yêu cầu làm thay đổi trạng thái bằng cookie SameSite kết hợp với CSRF token hoặc kiểm tra nguồn gốc (origin check). Độ tin cậy: 0.85
- Giới hạn tần suất gọi (rate-limit) đối với các điểm cuối (endpoints) đăng nhập, đăng ký, đặt lại mật khẩu và các điểm cuối tốn nhiều tài nguyên khác. Độ tin cậy: 0.90
- Trả về cùng một thông báo giống nhau cho cả trường hợp email không tồn tại và trường hợp sai mật khẩu để tránh lỗ hổng dò tìm tài khoản (account enumeration). Độ tin cậy: 0.80
- Xác thực các tệp tải lên theo loại nội dung (content type) và kích thước, lưu trữ chúng bên ngoài thư mục gốc của web (web root), và phục vụ chúng từ một tên miền riêng biệt. Độ tin cậy: 0.80
- Chặn các yêu cầu từ phía máy chủ gọi tới các dải IP nội bộ và IP metadata để ngăn chặn lỗ hổng SSRF. Độ tin cậy: 0.80
- Lưu giữ các thông tin bí mật (secrets) trong trình quản lý bí mật (secret manager) hoặc biến môi trường, tuyệt đối không lưu trong kho mã nguồn (repo) hay gói mã phía client (client bundle). Độ tin cậy: 0.95
- Chạy kiểm toán gói phụ thuộc (dependency audits) và tự động tạo PR cập nhật (Dependabot hoặc Renovate) trên mọi kho mã nguồn. Độ tin cậy: 0.85
- Ghi nhật ký (log) các sự kiện bảo mật như đăng nhập thành công, đăng nhập thất bại và thay đổi quyền hạn, nhưng tuyệt đối không ghi thông tin xác thực (credentials) vào log. Độ tin cậy: 0.85
- Cấu hình CORS với danh sách cho phép (allowlist) gồm các origin tường minh; không bao giờ phản chiếu nguyên xi origin của yêu cầu đi kèm với thông tin xác thực (credentials). Độ tin cậy: 0.90
- Sử dụng các API token có thời hạn ngắn, phạm vi quyền hạn hẹp và có khả năng thu hồi (revocable). Độ tin cậy: 0.85
- Thực hiện kiểm tra nhanh theo danh sách OWASP Top 10 trong quá trình duyệt mã (code review) cho mọi tính năng liên quan đến xác thực, dữ liệu đầu vào hoặc xử lý tệp tin. Độ tin cậy: 0.75
BackendWeb DevelopmentSecurity+2
F@f
0
Trình Đánh Giá Tiêu Đề Bộ Nhớ Đệm HTTP (HTTP Cache Header Reviewer)
Kỹ năng

Đánh giá các tiêu đề bộ nhớ đệm HTTP (Cache-Control, Expires, ETag, Last-Modified, Vary, Set-Cookie) cho các trang web, tài sản tĩnh (assets) và API. Tính toán thời gian còn hiệu lực (freshness) trên trình duyệt và CDN, gắn cờ cảnh báo các phản hồi chứa cookie hoặc dữ liệu cá nhân mà CDN có nguy cơ lưu chung, trang HTML được lưu đệm quá lâu, tài sản đã gắn mã băm (hashed) lại lưu đệm quá ngắn, thuộc tính immutable gắn trên các URL có nội dung thay đổi, phân mảnh bộ nhớ đệm do Vary, thiếu bộ xác thực (validator), lưu đệm theo suy đoán (heuristic caching) và lưu đệm cả các phản hồi lỗi. Đề xuất tiêu đề phù hợp cho từng loại tài nguyên. Bao gồm công cụ kiểm tra viết bằng thư viện chuẩn Python có hỗ trợ cờ `--self-test`.

---
name: http-cache-header-reviewer
description: Đánh giá các tiêu đề bộ nhớ đệm HTTP (Cache-Control, Expires, ETag, Last-Modified, Vary, Set-Cookie) cho các trang web, tài sản tĩnh (assets) và API. Tính toán thời gian còn hiệu lực (freshness) trên trình duyệt và CDN, gắn cờ cảnh báo các phản hồi chứa cookie hoặc dữ liệu cá nhân mà CDN có nguy cơ lưu chung, trang HTML được lưu đệm quá lâu, tài sản đã gắn mã băm (hashed) lại lưu đệm quá ngắn, thuộc tính immutable gắn trên các URL có nội dung thay đổi, phân mảnh bộ nhớ đệm do Vary, thiếu bộ xác thực (validator), lưu đệm theo suy đoán (heuristic caching) và lưu đệm cả các phản hồi lỗi. Đề xuất tiêu đề phù hợp cho từng loại tài nguyên. Bao gồm công cụ kiểm tra viết bằng thư viện chuẩn Python có hỗ trợ cờ --self-test.
---

# Trình Đánh Giá Tiêu Đề Bộ Nhớ Đệm HTTP (HTTP Cache Header Reviewer)

Bạn giúp các lập trình viên web cấu hình bộ nhớ đệm (caching) chuẩn xác: tăng tốc độ cho các lần truy cập lặp lại và giảm hóa đơn chi phí CDN mà không làm trang web bị cũ sau khi triển khai (deploy) hoặc tệ hơn là trả nhầm dữ liệu của người dùng này cho người dùng khác. Bạn thu thập các tiêu đề phản hồi (response headers) thực tế, giải thích cách từng tầng bộ nhớ đệm sẽ thực sự xử lý chúng, và đề xuất tiêu đề chuẩn cho từng loại tài nguyên kèm theo danh sách khắc phục ngắn gọn.

## Các tệp trong kỹ năng này

- `scripts/cache_audit.py` - đọc các khối phản hồi (đầu ra từ `curl -sI` hoặc DevTools) và in ra loại tài nguyên, thời gian sống (TTL) trên trình duyệt và CDN, cơ chế xác thực lại (revalidation), các cờ cảnh báo và tiêu đề `Cache-Control` được đề xuất (chỉ dùng thư viện chuẩn của Python 3)
- `references/cache-control-directives.md` - giải thích mọi chỉ thị (directive), quy tắc tính thời gian hiệu lực (freshness) và lưu đệm suy đoán (heuristic), bộ xác thực (validators), tiêu đề `Vary`, các mã trạng thái có thể lưu đệm
- `references/caching-strategy-playbook.md` - các tiêu đề khuyến nghị theo từng loại tài nguyên, đảm bảo an toàn khi triển khai (deploy), các lưu ý về CDN, cách thu thập và kiểm chứng tiêu đề
- `templates/cache-review-report.md` - mẫu báo cáo đối chiếu tiêu đề hiện tại với tiêu đề khuyến nghị cùng danh sách các mục cần sửa
- `examples/shop-responses.txt` - tám phản hồi từ một trang thương mại điện tử mẫu chứa các lỗi cấu hình phổ biến

## Quy trình thực hiện

### 1. Thu thập các tiêu đề
Yêu cầu danh sách các URL đại diện: trang chủ, một trang sau khi đăng nhập, một tệp JS/CSS có gắn mã băm (hashed), một tệp kịch bản không gắn mã băm, một hình ảnh, một phông chữ, một lệnh gọi API công khai, một lệnh gọi API cá nhân, và một phản hồi báo lỗi. Thu thập bằng lệnh `curl -sI <url>` (thêm `-H 'Cookie: ...'` hoặc `-H 'Authorization: ...'` đối với các yêu cầu đã đăng nhập, tuyệt đối không dán token thật vào báo cáo) hoặc dùng DevTools. Đặt từng phản hồi theo định dạng khối của tệp `examples/shop-responses.txt`: một dòng `### METHOD URL`, các dòng tùy chọn `> Request-Header: value`, dòng trạng thái (status line), rồi đến các tiêu đề phản hồi.

### 2. Chạy công cụ kiểm tra
```
python3 scripts/cache_audit.py examples/shop-responses.txt
python3 scripts/cache_audit.py headers.txt --json > cache-report.json
python3 scripts/cache_audit.py --self-test
```
Mã thoát (exit code) bằng 1 có nghĩa là phát hiện vấn đề mức độ nghiêm trọng cao, bằng 2 có nghĩa là không thể phân tích cú pháp dữ liệu đầu vào.

### 3. Giải thích các phát hiện
Thứ tự ưu tiên: quyền riêng tư là trên hết (AUTH_SHARED_CACHE, PUBLIC_WITH_COOKIE, PERSONAL_NOT_PRIVATE), tiếp theo là tính đúng đắn sau khi triển khai bản mới (HTML_LONG_TTL, IMMUTABLE_UNHASHED, ERROR_CACHED), sau đó đến hiệu năng (HASHED_SHORT_TTL, ASSET_NO_CACHING, VARY_FRAGMENTS, NO_VALIDATOR, HEURISTIC_ONLY) và tính chuẩn mực trong cấu hình (INVALID_DIRECTIVE, NO_STORE_CONFLICT, EXPIRES_ONLY, PRAGMA_ONLY). Sử dụng tệp `references/cache-control-directives.md` để giải thích lý do.

### 4. Đề xuất tiêu đề chuẩn
Sử dụng tệp `references/caching-strategy-playbook.md`. Đưa ra tiêu đề chính xác cho từng loại tài nguyên và chỉ rõ vị trí cần thiết lập trong hệ thống công nghệ (stack) của người dùng (Nginx `add_header`, Express `res.set`, Next.js `headers()`, siêu dữ liệu S3/CloudFront, quy tắc Cloudflare). Nhắc tới nguyên tắc triển khai: lưu đệm tài sản có mã băm trong một năm, còn tệp HTML thì luôn xác thực lại (revalidate).

### 5. Xác minh và lập báo cáo
Điền thông tin vào mẫu `templates/cache-review-report.md`. Sau khi thay đổi cấu hình, hãy thu thập lại các tiêu đề và chạy lại công cụ kiểm tra; đồng thời kiểm tra các tiêu đề HIT/MISS của CDN.

## Ví dụ kết quả
Đối với tệp `examples/shop-responses.txt`: 5 phát hiện mức độ cao (trang HTML trang chủ lưu đệm 1 ngày, tệp `widget.js` đặt `immutable` nhưng không có mã băm, API giỏ hàng có cookie phiên làm việc lại bị CDN lưu đệm trong 2 phút, trang đăng nhập có `Set-Cookie` nhưng bị đánh dấu `public` và lưu đệm 10 phút), 4 phát hiện mức trung bình (`app.3f9a1c7b.js` chỉ lưu đệm 1 giờ, `hero.jpg` lưu đệm theo suy đoán 8,1 ngày, dùng `Vary: Cookie`, lỗi 503 bị lưu đệm trong 1 giờ) và 1 phát hiện mức thấp (API giỏ hàng không có bộ xác thực); chỉ có duy nhất tệp `vendor.81d2e0aa.css` là đã được cấu hình lý tưởng.

## Quy tắc

- Luôn coi bất kỳ nguy cơ nào khiến bộ nhớ đệm dùng chung (shared cache) lưu trữ dữ liệu cá nhân là vấn đề ưu tiên hàng đầu.
- Không yêu cầu hoặc lưu giữ cookie hay token thật; chỉ cần dùng các giá trị giữ chỗ (placeholders) là đủ.
- Khuyến nghị xóa bộ nhớ đệm (purge) hoặc đánh phiên bản URL (URL versioning) khi thay đổi thời gian sống (TTL) của các tài nguyên đang chạy thực tế.
- Nêu rõ khi nào hành vi phụ thuộc vào cấu hình riêng của nhà cung cấp CDN và đề xuất người dùng kiểm tra cấu hình đó.
FILE:references/cache-control-directives.md
# Các Chỉ Thị Cache-Control và Quy Tắc Tính Thời Gian Hiệu Lực

## Các chỉ thị trong phản hồi (Response directives)
| Chỉ thị | Ý nghĩa |
|---|---|
| max-age=N | Còn hiệu lực (fresh) trong N giây ở bất kỳ bộ nhớ đệm nào (cả trình duyệt lẫn bộ nhớ đệm dùng chung) |
| s-maxage=N | Ghi đè giá trị max-age chỉ dành riêng cho các bộ nhớ đệm dùng chung (CDN, proxy) |
| no-cache | Có thể được lưu trữ, nhưng bắt buộc phải xác thực lại (revalidate) với máy chủ gốc trước mỗi lần sử dụng |
| no-store | Tuyệt đối không được lưu trữ ở bất cứ đâu; ghi đè lên mọi chỉ thị khác |
| private | Chỉ trình duyệt của người dùng mới được phép lưu trữ; các CDN và proxy không được phép lưu |
| public | Bộ nhớ đệm dùng chung được phép lưu trữ ngay cả khi thông thường phản hồi đó không được lưu đệm (ví dụ khi có tiêu đề Authorization) |
| must-revalidate | Một khi đã hết hạn (stale), tuyệt đối không được phục vụ nếu chưa xác thực lại thành công với máy chủ gốc |
| proxy-revalidate | Tương tự như must-revalidate, nhưng chỉ áp dụng cho các bộ nhớ đệm dùng chung |
| immutable | Sẽ không bao giờ thay đổi trong suốt thời gian còn hiệu lực; trình duyệt bỏ qua bước xác thực lại khi người dùng tải lại trang |
| stale-while-revalidate=N | Cho phép phục vụ bản cũ đã hết hạn trong N giây trong khi âm thầm làm mới dưới nền |
| stale-if-error=N | Cho phép phục vụ bản cũ đã hết hạn trong N giây nếu máy chủ gốc trả về lỗi 5xx hoặc không thể kết nối |
| no-transform | Các trạm trung gian không được phép sửa đổi phần thân phản hồi (như nén lại hình ảnh, v.v.) |

Những nhầm lẫn phổ biến: `no-cache` KHÔNG có nghĩa là "không lưu vào bộ nhớ đệm" (đó là chức năng của `no-store`); giá trị của `max-age` bắt buộc phải là số nguyên giây (`max-age=10m` là không hợp lệ và sẽ bị bỏ qua); `Pragma: no-cache` chỉ có tác dụng trên chiều yêu cầu gửi đi (request).

## Thời gian sống còn hiệu lực (Freshness lifetime - RFC 9111)
1. Bộ nhớ đệm dùng chung (Shared cache): ưu tiên `s-maxage`, nếu không có thì lấy `max-age`, nếu không có nữa thì lấy `Expires - Date`.
2. Trình duyệt (Browser): ưu tiên `max-age`, nếu không có thì lấy `Expires - Date`.
3. Khi không có cả hai tiêu đề trên nhưng có `Last-Modified`: các bộ nhớ đệm có thể tính theo suy đoán (heuristic), thường lấy bằng 10% của `(Date - Last-Modified)`. Một tệp được sửa đổi lần cuối cách đây 80 ngày có thể được tái sử dụng suốt 8 ngày mà không cần kiểm tra lại.
4. Chỉ thị `private` đặt thời gian sống trên bộ nhớ đệm dùng chung về 0. Chỉ thị `no-store` đặt cả hai về 0.

## Bộ xác thực (Validators) và cơ chế xác thực lại (Revalidation)
- ETag (mạnh `"abc"` hoặc yếu `W/"abc"`) -> phía máy khách gửi kèm tiêu đề `If-None-Match`.
- Last-Modified -> phía máy khách gửi kèm tiêu đề `If-Modified-Since`.
- Nếu khớp, máy chủ trả về mã `304 Not Modified` không kèm phần thân (body), rất nhẹ và tiết kiệm băng thông. Nếu không có bộ xác thực, mỗi lần xác thực lại đều phải tải về toàn bộ phần thân phản hồi.

## Tiêu đề Vary
- `Vary: Accept-Encoding` là bình thường và an toàn.
- `Vary: Cookie`, `User-Agent` hoặc `Referer` sẽ tạo ra một bản ghi bộ nhớ đệm riêng cho mỗi giá trị khác nhau, đồng nghĩa với việc hầu như không bao giờ trúng bộ nhớ đệm (cache hit) trên CDN.
- `Vary: *` có nghĩa là phản hồi này không bao giờ có thể tái sử dụng.

## Các mã trạng thái (Status codes)
Được phép lưu đệm theo mặc định khi còn hiệu lực: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501. Các phản hồi lỗi (4xx/5xx) chỉ nên lưu đệm rất ngắn hoặc không lưu đệm, nếu không CDN có thể hiển thị trang báo sập hệ thống suốt nhiều giờ liền. Hãy sử dụng `stale-if-error` để phục vụ bản sao hợp lệ gần nhất thay thế.
FILE:references/caching-strategy-playbook.md
# Cẩm Nang Chiến Lược Bộ Nhớ Đệm

## Các tiêu đề khuyến nghị theo từng loại tài nguyên
| Tài nguyên | Cache-Control | Ghi chú |
|---|---|---|
| Trang HTML, khung ứng dụng (app shell) | `no-cache` + ETag | Luôn mới sau mỗi lần triển khai (deploy); phản hồi 304 giúp tiết kiệm băng thông |
| Trang HTML tiếp thị (Marketing) trên CDN | `public, max-age=0, s-maxage=300, stale-while-revalidate=60` | Xóa bộ nhớ đệm CDN (purge) khi xuất bản nội dung mới |
| Tệp JS/CSS có mã băm nội dung (`app.3f9a1c7b.js`) | `public, max-age=31536000, immutable` | Bản dựng (build) mới = URL mới |
| Tệp JS/CSS không có mã băm | `public, max-age=300, stale-while-revalidate=86400` | Hoặc bổ sung cơ chế gắn mã băm vào quy trình build |
| Hình ảnh, tệp người dùng tải lên có URL cố định | `public, max-age=86400, stale-while-revalidate=604800` | Đánh phiên bản cho URL khi hình ảnh thay đổi |
| Phông chữ (URL có đánh phiên bản) | `public, max-age=31536000, immutable` | Đồng thời gửi kèm các tiêu đề CORS |
| Dữ liệu API công khai (giá cả, danh mục sản phẩm) | `public, max-age=30, s-maxage=300, stale-if-error=600` | Giữ TTL ngắn; xóa bộ nhớ đệm khi dữ liệu thay đổi |
| Dữ liệu API cá nhân (giỏ hàng, hồ sơ cá nhân) | `private, no-cache` + ETag | Tuyệt đối không đặt là public |
| Thông tin bí mật, ngân hàng, hồ sơ y tế, token dùng một lần | `no-store` | Áp dụng cả trên trang đăng xuất và trang quản lý tài khoản |
| Chuyển hướng 301 (Redirect 301) | Bắt đầu với `max-age=86400` | Trình duyệt có thể lưu đệm vĩnh viễn một mã 301 không kèm giới hạn thời gian |
| Các lỗi 5xx | `no-store` hoặc `max-age=0`, kết hợp dùng `stale-if-error` trên các phản hồi thành công | |

## Đảm bảo an toàn khi triển khai (Deploy safety)
1. Gắn mã băm (hash) vào tên của mọi tệp tài sản được tạo ra khi build, sau đó lưu đệm chúng trong thời hạn một năm.
2. Cho tệp HTML xác thực lại (revalidate) để nó luôn trỏ tới các tệp có mã băm mới nhất hiện tại.
3. Giữ các tệp tài sản gắn mã băm của bản cũ tiếp tục trực tuyến ít nhất qua một đợt phát hành để các tab trình duyệt đang mở sẵn vẫn hoạt động bình thường.
4. Tuyệt đối không gắn chỉ thị `immutable` lên một URL mà nội dung của nó có thể thay đổi.

## Các lưu ý về CDN
- Hầu hết các CDN chỉ bỏ qua `private`/`no-store` khi bị cấu hình sai; hãy kiểm tra tùy chọn "tôn trọng tiêu đề từ máy chủ gốc" (respect origin headers).
- Nhiều CDN mặc định không lưu đệm các phản hồi có chứa `Set-Cookie`; nhưng một số CDN vẫn lưu nếu có đặt chỉ thị `public`. Hãy loại bỏ `Set-Cookie` khỏi các phản hồi tài nguyên tĩnh.
- Sử dụng tiêu đề `Surrogate-Control` hoặc `CDN-Cache-Control` nếu CDN có hỗ trợ và bạn muốn thiết lập TTL trên CDN tách biệt với TTL trên trình duyệt.
- Chuẩn hóa khóa bộ nhớ đệm (cache key) (bỏ qua các chuỗi truy vấn theo dõi quảng cáo như `utm_*`).

## Cách thu thập các tiêu đề
```
curl -sI https://example.com/ ; curl -sI -H 'Cookie: session=...' https://example.com/api/me
curl -s -o /dev/null -D - https://example.com/assets/app.js   # Dùng GET thay vì HEAD
```
Một số máy chủ trả lời phương thức HEAD khác với phương thức GET; khi nghi ngờ, hãy sử dụng cú pháp GET. Trong Chrome DevTools: mở tab Network, chọn một yêu cầu, vào mục Headers, tìm phần Response Headers (sao chép dưới dạng văn bản).

## Kiểm tra kết quả
- Tải lại trang hai lần trong DevTools: các tài sản có gắn mã băm phải hiển thị "(memory cache)" hoặc "(disk cache)", còn trang HTML phải hiển thị mã 304 hoặc 200.
- Đối với CDN: quan sát các tiêu đề `Age`, `X-Cache` hoặc `CF-Cache-Status` (HIT / MISS / BYPASS).
FILE:templates/cache-review-report.md
# Báo Cáo Đánh Giá Bộ Nhớ Đệm HTTP - <site / service> - <date>

**Phạm vi:** <URLs or HAR file>  **CDN:** <name or none>  **Thu thập bằng:** <curl -sI / DevTools / HAR>

## Tóm tắt
- Số phản hồi đã đánh giá: <n>  Mức Cao: <n>  Mức Trung bình: <n>  Mức Thấp: <n>
- Rủi ro lớn nhất: <ví dụ: API cá nhân bị lưu đệm trên CDN>
- Cải thiện mang lại hiệu quả lớn nhất: <ví dụ: lưu đệm một năm cho các tài sản tĩnh có gắn mã băm>

## Đối chiếu hiện tại và khuyến nghị
| Tài nguyên | Loại | Cache-Control hiện tại | TTL Trình duyệt / CDN | Cờ cảnh báo | Khuyến nghị |
|---|---|---|---|---|---|
| | | | | | |

## Danh sách cần khắc phục
| Ưu tiên | Thay đổi cần làm | Vị trí sửa (máy chủ, framework, quy tắc CDN) | Người phụ trách | Hoàn thành |
|---|---|---|---|---|
| 1 | | | | [ ] |

## Xác minh
- [ ] Chạy lại lệnh `python3 scripts/cache_audit.py` trên các tiêu đề mới thu thập: không còn phát hiện mức độ cao
- [ ] CDN hiển thị HIT đối với các tệp tĩnh, BYPASS/MISS đối với API dữ liệu cá nhân
- [ ] Kiểm thử triển khai (deploy test): trang HTML mới tải đúng các tài sản mới mà không cần ép tải lại trang (hard refresh)
FILE:examples/shop-responses.txt
# Responses captured with: curl -sI <url>  (request headers sent are listed with "> ")
### GET https://shop.example/
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=86400
date: Wed, 21 Oct 2026 07:00:00 GMT

### GET https://shop.example/assets/app.3f9a1c7b.js
HTTP/2 200
content-type: application/javascript
cache-control: public, max-age=3600
etag: "3f9a1c7b"

### GET https://shop.example/assets/vendor.81d2e0aa.css
HTTP/2 200
content-type: text/css
cache-control: public, max-age=31536000, immutable
etag: "81d2e0aa"

### GET https://shop.example/js/widget.js
HTTP/2 200
content-type: application/javascript
cache-control: public, max-age=31536000, immutable

### GET https://shop.example/img/hero.jpg
HTTP/2 200
content-type: image/jpeg
date: Wed, 21 Oct 2026 07:00:00 GMT
last-modified: Sat, 01 Aug 2026 07:00:00 GMT

### GET https://shop.example/api/cart
> Cookie: session=abc123
HTTP/2 200
content-type: application/json
cache-control: public, s-maxage=120
vary: Cookie

### POST https://shop.example/login
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=600
set-cookie: session=xyz789; HttpOnly; Secure; SameSite=Lax

### GET https://shop.example/api/products?page=1
HTTP/2 503
content-type: application/json
cache-control: public, max-age=3600
FILE:scripts/cache_audit.py
#!/usr/bin/env python3
"""HTTP caching header reviewer (Python 3 standard library only).

Input: a text file with one block per response, as copied from `curl -sI`, browser
devtools or a HAR export:
  ### GET https://shop.example/assets/app.3f9a1c7b.js
  > Authorization: Bearer ...          (optional request headers start with "> ")
  HTTP/2 200
  content-type: application/javascript
  cache-control: public, max-age=31536000, immutable
  etag: "3f9a1c7b"
Blank lines and lines starting with "#" (but not "###") are ignored.

For each response it works out the kind (html, hashed-asset, asset, api, image, font, other),
browser and shared-cache (CDN) freshness in seconds (s-maxage > max-age > Expires - Date >
10% heuristic from Last-Modified), whether it can be revalidated, and flags:
  NO_STORE_CONFLICT, PUBLIC_WITH_COOKIE, AUTH_SHARED_CACHE, PERSONAL_NOT_PRIVATE, VARY_STAR,
  VARY_FRAGMENTS, HTML_LONG_TTL, HASHED_SHORT_TTL, IMMUTABLE_UNHASHED, ASSET_NO_CACHING,
  NO_VALIDATOR, HEURISTIC_ONLY, EXPIRES_ONLY, PRAGMA_ONLY, ERROR_CACHED, INVALID_DIRECTIVE.
It suggests a Cache-Control value per kind.
Usage:
  python3 cache_audit.py responses.txt
  python3 cache_audit.py responses.txt --json
  python3 cache_audit.py --self-test
Exit code: 0 OK, 1 high-severity flags found, 2 bad input.
"""
import argparse
import email.utils
import json
import re
import sys

KNOWN = {"max-age", "s-maxage", "no-cache", "no-store", "no-transform", "must-revalidate",
         "proxy-revalidate", "must-understand", "private", "public", "immutable",
         "stale-while-revalidate", "stale-if-error"}
NEEDS_INT = {"max-age", "s-maxage", "stale-while-revalidate", "stale-if-error"}
HASH_RE = re.compile(r"[.\-_]([0-9a-f]{8,}|[0-9A-Za-z_-]{8,}(?=\.[a-z0-9]+$))\.[a-z0-9]+$")
SEVERITY = {"NO_STORE_CONFLICT": "low", "PUBLIC_WITH_COOKIE": "high", "AUTH_SHARED_CACHE": "high",
            "PERSONAL_NOT_PRIVATE": "medium", "VARY_STAR": "medium", "VARY_FRAGMENTS": "medium",
            "HTML_LONG_TTL": "high", "HASHED_SHORT_TTL": "medium", "IMMUTABLE_UNHASHED": "high",
            "ASSET_NO_CACHING": "medium", "NO_VALIDATOR": "low", "HEURISTIC_ONLY": "medium",
            "EXPIRES_ONLY": "low", "PRAGMA_ONLY": "low", "ERROR_CACHED": "medium",
            "INVALID_DIRECTIVE": "medium"}
RECOMMEND = {
    "html": "no-cache (plus ETag or Last-Modified), or max-age=0, must-revalidate",
    "hashed-asset": "public, max-age=31536000, immutable",
    "asset": "public, max-age=3600, stale-while-revalidate=86400 (or add a content hash to the file name)",
    "image": "public, max-age=86400, stale-while-revalidate=604800",
    "font": "public, max-age=31536000, immutable (if the URL is versioned)",
    "api": "private, no-cache for user data; public, max-age=60, s-maxage=300 only for truly public data",
    "personal": "private, no-cache (or no-store for sensitive data)",
    "other": "set an explicit Cache-Control",
}


class CacheError(ValueError):
    pass


def parse_cc(value):
    """Return (directives dict, problems list)."""
    d, problems = {}, []
    for part in [p.strip() for p in value.split(",") if p.strip()]:
        k, eq, v = part.partition("=")
        k = k.strip().lower()
        v = v.strip().strip('"')
        if k not in KNOWN:
            problems.append(f"unknown directive {k!r}")
            continue
        if k in NEEDS_INT:
            if not re.fullmatch(r"\d+", v or ""):
                problems.append(f"{k} needs a whole number of seconds, got {v!r}")
                continue
            d[k] = int(v)
        else:
            d[k] = True if not eq else v
    return d, problems


def parse_blocks(text):
    blocks, cur = [], None
    for raw in text.splitlines():
        line = raw.rstrip("\r")
        if line.startswith("###"):
            cur = {"label": line[3:].strip(), "status": None, "headers": {}, "request": {}}
            blocks.append(cur)
            continue
        if not line.strip() or line.lstrip().startswith("#"):
            continue
        if cur is None:
            raise CacheError("file must start with a '### METHOD URL' line")
        if line.startswith(">"):
            k, _, v = line[1:].strip().partition(":")
            cur["request"][k.strip().lower()] = v.strip()
            continue
        m = re.match(r"HTTP/[\d.]+\s+(\d{3})", line)
        if m:
            cur["status"] = int(m.group(1))
            continue
        k, colon, v = line.partition(":")
        if not colon:
            raise CacheError(f"cannot parse header line {line!r} in block {cur['label']!r}")
        k = k.strip().lower()
        if k in cur["headers"] and k in ("cache-control", "vary"):
            cur["headers"][k] += ", " + v.strip()
        elif k == "set-cookie":
            cur["headers"].setdefault(k, []).append(v.strip())
        else:
            cur["headers"][k] = v.strip()
    if not blocks:
        raise CacheError("no response blocks found")
    return blocks


def url_of(label):
    parts = label.split()
    return parts[-1] if parts else ""


def kind_of(url, ctype):
    path = re.sub(r"[?#].*$", "", url)
    name = path.rsplit("/", 1)[-1].lower()
    ctype = ctype.lower()
    hashed = bool(HASH_RE.search(name)) and bool(re.search(r"\d", name))
    if "text/html" in ctype or name.endswith(".html") or (not ctype and "." not in name):
        return "html", False
    if "json" in ctype or "/api/" in path or "graphql" in path:
        return "api", False
    if name.endswith((".woff2", ".woff", ".ttf", ".otf")) or "font" in ctype:
        return "font", hashed
    if ctype.startswith("image/") or name.endswith((".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".svg", ".ico")):
        return ("hashed-asset" if hashed else "image"), hashed
    if name.endswith((".js", ".mjs", ".css", ".map", ".wasm")) or "javascript" in ctype or "text/css" in ctype:
        return ("hashed-asset" if hashed else "asset"), hashed
    return "other", hashed


def http_date(v):
    try:
        return email.utils.parsedate_to_datetime(v)
    except (TypeError, ValueError, IndexError):
        return None


def review(block):
    h, req = block["headers"], block["request"]
    status = block["status"] or 200
    url = url_of(block["label"])
    kind, hashed = kind_of(url, h.get("content-type", ""))
    cc, problems = parse_cc(h.get("cache-control", ""))
    flags = []

    def flag(code, detail):
        flags.append({"code": code, "severity": SEVERITY[code], "detail": detail})

    for p in problems:
        flag("INVALID_DIRECTIVE", p)
    cookies = h.get("set-cookie", [])
    auth = "authorization" in req or "cookie" in req
    date, expires, lastmod = http_date(h.get("date")), h.get("expires"), http_date(h.get("last-modified"))
    no_store = "no-store" in cc
    heuristic = False
    if no_store:
        browser = shared = 0
    else:
        browser = cc.get("max-age")
        if browser is None and expires is not None:
            ex = http_date(expires)
            browser = max(0, int((ex - date).total_seconds())) if ex and date else 0
        if browser is None and "no-cache" not in cc and lastmod and date and status in (200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501):
            browser = max(0, int((date - lastmod).total_seconds() / 10))
            heuristic = True
        browser = browser or 0
        shared = 0 if "private" in cc else cc.get("s-maxage", browser)
    if "no-cache" in cc:
        browser_eff, shared_eff = 0, 0
    else:
        browser_eff, shared_eff = browser, shared
    validator = "etag" in h or "last-modified" in h
    personal = bool(cookies) or auth

    if no_store and any(k in cc for k in ("max-age", "s-maxage", "public", "immutable")):
        flag("NO_STORE_CONFLICT", "no-store overrides the other directives; remove the extras so intent is clear")
    if cookies and ("public" in cc or "s-maxage" in cc) and not no_store:
        flag("PUBLIC_WITH_COOKIE", "response sets a cookie but allows shared caches; a CDN may serve one user's cookie to others")
    if auth and shared > 0 and ("public" in cc or "s-maxage" in cc) and kind in ("api", "html", "other"):
        flag("AUTH_SHARED_CACHE", "authenticated request with a response that shared caches may store; personal data can leak across users")
    elif personal and kind in ("api", "html", "other") and not no_store and "private" not in cc \
            and not any(f["code"] == "PUBLIC_WITH_COOKIE" for f in flags):
        flag("PERSONAL_NOT_PRIVATE", "personalized response without private or no-store")
    vary = [v.strip().lower() for v in h.get("vary", "").split(",") if v.strip()]
    if "*" in vary:
        flag("VARY_STAR", "Vary: * makes the response effectively uncacheable")
    frag = [v for v in vary if v in ("user-agent", "cookie", "referer")]
    if frag and shared > 0:
        flag("VARY_FRAGMENTS", f"Vary on {', '.join(frag)} splits the CDN cache into many copies; vary on a normalized header instead")
    if kind == "html" and browser_eff > 300:
        flag("HTML_LONG_TTL", f"HTML cached {fmt_s(browser_eff)} without revalidation; users keep old pages that point to deleted assets after a deploy")
    if kind == "hashed-asset" and browser_eff < 30 * 86400 and not no_store:
        flag("HASHED_SHORT_TTL", f"content-hashed file cached only {fmt_s(browser_eff)}; it can be cached for a year with immutable")
    if "immutable" in cc and not hashed:
        flag("IMMUTABLE_UNHASHED", "immutable on a URL without a content hash; browsers will not pick up changes until max-age expires")
    if kind in ("asset", "image", "font") and (no_store or "no-cache" in cc) and not personal:
        flag("ASSET_NO_CACHING", "static file is never reused from cache; every page view downloads or revalidates it")
    if ("no-cache" in cc or browser_eff == 0) and not no_store and not validator and status == 200:
        flag("NO_VALIDATOR", "must revalidate but has no ETag or Last-Modified, so every check downloads the full body")
    if heuristic:
        flag("HEURISTIC_ONLY", f"no Cache-Control or Expires; caches guess {fmt_s(browser)} from Last-Modified")
    if "cache-control" not in h and expires is not None:
        flag("EXPIRES_ONLY", "only the legacy Expires header; add Cache-Control max-age")
    if "cache-control" not in h and "pragma" in h:
        flag("PRAGMA_ONLY", "Pragma: no-cache is a request header and has no effect on responses; use Cache-Control")
    if status >= 400 and max(browser_eff, shared_eff) > 300:
        flag("ERROR_CACHED", f"status {status} cached for {fmt_s(max(browser_eff, shared_eff))}; a brief error becomes a long outage")
    rec_kind = "personal" if personal and kind in ("api", "html", "other") else kind
    return {"label": block["label"], "status": status, "kind": kind, "hashed": hashed,
            "cache_control": h.get("cache-control"), "browser_ttl_s": browser_eff, "shared_ttl_s": shared_eff,
            "revalidate": "no-cache" in cc or "must-revalidate" in cc, "validator": validator,
            "stale_while_revalidate_s": cc.get("stale-while-revalidate"), "flags": flags,
            "recommended": RECOMMEND[rec_kind]}


def fmt_s(s):
    if s is None:
        return "-"
    for unit, n in (("d", 86400), ("h", 3600), ("m", 60)):
        if s >= n:
            v = s / n
            return f"{v:.0f}{unit}" if v == int(v) else f"{v:.1f}{unit}"
    return f"{s}s"


def audit(text):
    items = [review(b) for b in parse_blocks(text)]
    return {"items": items, "high_flags": sum(1 for i in items for f in i["flags"] if f["severity"] == "high")}


def to_markdown(r):
    out = ["# HTTP cache header review", "",
           "| Response | Status | Kind | Browser TTL | CDN TTL | Revalidate | Validator | Flags |",
           "|---|---|---|---|---|---|---|---|"]
    for i in r["items"]:
        out.append(f"| {i['label']} | {i['status']} | {i['kind']} | {fmt_s(i['browser_ttl_s'])} | {fmt_s(i['shared_ttl_s'])} | "
                   f"{'yes' if i['revalidate'] else 'no'} | {'yes' if i['validator'] else 'no'} | {', '.join(f['code'] for f in i['flags']) or '-'} |")
    out += ["", "## Findings"]
    n = 0
    for sev in ("high", "medium", "low"):
        for i in r["items"]:
            for f in i["flags"]:
                if f["severity"] == sev:
                    n += 1
                    out.append(f"- **{sev.upper()} {f['code']}** {i['label']}: {f['detail']}")
    if not n:
        out.append("- No findings.")
    out += ["", "## Suggested Cache-Control"]
    for i in r["items"]:
        if i["flags"]:
            out.append(f"- {i['label']}: `{i['recommended']}` (now: `{i['cache_control'] or 'none'}`)")
    return "\n".join(out)


def self_test():
    cases = []

    def ok(name, cond):
        cases.append((name, bool(cond)))

    def one(url, *lines):
        return review(parse_blocks("### GET " + url + "\nHTTP/1.1 200 OK\n" + "\n".join(lines))[0])

    def codes(item):
        return {f["code"] for f in item["flags"]}

    ok("parse max-age", parse_cc("public, max-age=600")[0] == {"public": True, "max-age": 600})
    ok("bad max-age reported", parse_cc("max-age=10m")[1])
    ok("unknown directive reported", parse_cc("maxage=60")[1])
    ok("hashed js detected", kind_of("https://x/app.3f9a1c7b.js", "") == ("hashed-asset", True))
    ok("unhashed css", kind_of("https://x/styles.css", "text/css") == ("asset", False))
    ok("html by content type", kind_of("https://x/", "text/html; charset=utf-8")[0] == "html")
    ok("api by json", kind_of("https://x/api/cart", "application/json")[0] == "api")
    i = one("https://x/app.3f9a1c7b.js", "Cache-Control: public, max-age=31536000, immutable", "ETag: \"a\"")
    ok("ideal hashed asset clean", not codes(i) and i["browser_ttl_s"] == 31536000)
    i = one("https://x/app.3f9a1c7b.js", "Cache-Control: public, max-age=3600")
    ok("hashed short ttl", "HASHED_SHORT_TTL" in codes(i))
    i = one("https://x/main.js", "Cache-Control: public, max-age=31536000, immutable")
    ok("immutable unhashed", "IMMUTABLE_UNHASHED" in codes(i))
    i = one("https://x/", "Content-Type: text/html", "Cache-Control: max-age=86400")
    ok("html long ttl", "HTML_LONG_TTL" in codes(i))
    i = one("https://x/", "Content-Type: text/html", "Cache-Control: no-cache", "ETag: \"1\"")
    ok("html no-cache with etag clean", not codes(i) and i["browser_ttl_s"] == 0)
    i = one("https://x/", "Content-Type: text/html", "Cache-Control: no-cache")
    ok("no validator", "NO_VALIDATOR" in codes(i))
    i = one("https://x/login", "Content-Type: text/html", "Cache-Control: public, max-age=600", "Set-Cookie: sid=1")
    ok("public with cookie", "PUBLIC_WITH_COOKIE" in codes(i))
    i = review(parse_blocks("### GET https://x/api/me\n> Authorization: Bearer t\nHTTP/2 200\ncontent-type: application/json\ncache-control: public, s-maxage=300")[0])
    ok("auth shared cache", "AUTH_SHARED_CACHE" in codes(i) and i["shared_ttl_s"] == 300)
    i = review(parse_blocks("### GET https://x/api/me\n> Cookie: sid=1\nHTTP/2 200\ncontent-type: application/json\ncache-control: max-age=60")[0])
    ok("personal not private", "PERSONAL_NOT_PRIVATE" in codes(i))
    i = one("https://x/api/me", "Content-Type: application/json", "Cache-Control: private, max-age=60")
    ok("private hides from cdn", i["shared_ttl_s"] == 0 and i["browser_ttl_s"] == 60)
    i = one("https://x/a.css", "Cache-Control: no-store, max-age=60")
    ok("no-store conflict and asset no caching", {"NO_STORE_CONFLICT", "ASSET_NO_CACHING"} <= codes(i))
    i = one("https://x/a.css", "Cache-Control: public, max-age=600", "Vary: *")
    ok("vary star", "VARY_STAR" in codes(i))
    i = one("https://x/a.css", "Cache-Control: public, max-age=600", "Vary: Accept-Encoding, User-Agent")
    ok("vary user-agent fragments", "VARY_FRAGMENTS" in codes(i))
    i = one("https://x/logo.png", "Date: Wed, 21 Oct 2026 10:00:00 GMT", "Last-Modified: Mon, 11 Oct 2026 10:00:00 GMT")
    ok("heuristic 10% of 10 days = 1 day", "HEURISTIC_ONLY" in codes(i) and i["browser_ttl_s"] == 86400)
    i = one("https://x/logo.png", "Date: Wed, 21 Oct 2026 10:00:00 GMT", "Expires: Wed, 21 Oct 2026 11:00:00 GMT")
    ok("expires only gives 1h", "EXPIRES_ONLY" in codes(i) and i["browser_ttl_s"] == 3600)
    i = one("https://x/", "Content-Type: text/html", "Pragma: no-cache")
    ok("pragma only", "PRAGMA_ONLY" in codes(i))
    i = review(parse_blocks("### GET https://x/a.css\nHTTP/1.1 503 Service Unavailable\ncache-control: public, max-age=3600")[0])
    ok("error cached", "ERROR_CACHED" in codes(i))
    ok("repeated cache-control merged", parse_blocks("### GET https://x/a\nHTTP/1.1 200\ncache-control: public\ncache-control: max-age=5")[0]["headers"]["cache-control"] == "public, max-age=5")
    try:
        parse_blocks("HTTP/1.1 200 OK\n")
        ok("missing ### rejected", False)
    except CacheError:
        ok("missing ### rejected", True)
    ok("fmt_s", fmt_s(31536000) == "365d" and fmt_s(600) == "10m" and fmt_s(45) == "45s" and fmt_s(699840) == "8.1d")
    passed = sum(c for _, c in cases)
    for name, c in cases:
        print(("PASS " if c else "FAIL ") + name)
    print(f"{passed}/{len(cases)} passed")
    return 0 if passed == len(cases) else 1


def main(argv=None):
    ap = argparse.ArgumentParser(description="Review HTTP caching headers.")
    ap.add_argument("file", nargs="?", help="responses text file ('-' for stdin)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test()
    if not a.file:
        ap.error("responses file required (or --self-test)")
    try:
        text = sys.stdin.read() if a.file == "-" else open(a.file, encoding="utf-8").read()
        r = audit(text)
    except (OSError, CacheError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(r, indent=2) if a.json else to_markdown(r))
    return 1 if r["high_flags"] else 0


if __name__ == "__main__":
    sys.exit(main())
BackendWeb DevelopmentSecurity+2
F@f
0
Hiệu Năng Web taste
Văn bản

Các ưu tiên mang tính định kiến nhằm giúp trang web tải nhanh và duy trì khả năng phản hồi mượt mà cho người dùng thực tế.

# Phong cách (taste) tối ưu hiệu năng Web của một kỹ sư Frontend
- Đo lường các chỉ số Core Web Vitals (LCP, INP, CLS) từ người dùng thực tế, chứ không chỉ dựa vào các lần chạy Lighthouse trong môi trường thử nghiệm (lab). Độ tin cậy: 0.90
- Thiết lập ngân sách hiệu năng (performance budget) cho dung lượng JavaScript và chỉ số LCP, đồng thời cho dừng lỗi CI khi có thay đổi vượt quá giới hạn đó. Độ tin cậy: 0.80
- Kết xuất (render) khung nhìn đầu tiên trên máy chủ hoặc dưới dạng tĩnh; không phân phối một trang trắng phải chờ tải xong JavaScript. Độ tin cậy: 0.90
- Giảm lượng mã JavaScript gửi đi trước khi tối ưu hóa số JavaScript mà bạn gửi. Độ tin cậy: 0.90
- Chia nhỏ mã (code splitting) theo từng tuyến đường (route) và tải trễ (lazy-load) các thành phần nặng như trình soạn thảo, biểu đồ và bản đồ. Độ tin cậy: 0.85
- Kiểm tra bằng công cụ phân tích gói mã (bundle analyzer) trước khi thêm một thư viện phụ thuộc, và ưu tiên các thư viện nhỏ gọn, hỗ trợ loại bỏ mã thừa (tree-shakable). Độ tin cậy: 0.80
- Phân phối hình ảnh ở định dạng AVIF hoặc WebP với thuộc tính srcset đáp ứng (responsive) cùng chiều rộng (width) và chiều cao (height) tường minh. Độ tin cậy: 0.90
- Đánh dấu hình ảnh LCP bằng thuộc tính fetchpriority="high" và tuyệt đối không tải trễ (lazy-load) nó. Độ tin cậy: 0.85
- Tải trễ các hình ảnh và thẻ iframe nằm bên dưới màn hình đầu tiên (below-the-fold) bằng thuộc tính loading="lazy". Độ tin cậy: 0.85
- Tự lưu trữ (self-host) phông chữ, rút gọn bộ ký tự (subset), tải trước (preload) phông chữ chính và sử dụng thuộc tính font-display: swap. Độ tin cậy: 0.80
- Dành sẵn không gian trống cho quảng cáo, nội dung nhúng và nội dung tải muộn để bố cục trang không bị xê dịch. Độ tin cậy: 0.85
- Chia nhỏ các tác vụ kéo dài trên 50 ms và nhường quyền xử lý lại cho luồng chính (main thread) để giữ chỉ số INP ở mức thấp. Độ tin cậy: 0.80
- Tải các tập lệnh của bên thứ ba bằng thuộc tính defer hoặc sau khi có tương tác của người dùng, đồng thời kiểm tra giá trị thực sự của từng tập lệnh. Độ tin cậy: 0.85
- Lưu bộ nhớ đệm (cache) cho các tài sản tĩnh đã gắn mã băm (hashed) với tiêu đề Cache-Control: immutable trong thời hạn một năm. Độ tin cậy: 0.90
- Phân phối mọi tài nguyên qua giao thức HTTP/2 hoặc HTTP/3 từ mạng phân phối nội dung (CDN) có bật nén Brotli. Độ tin cậy: 0.85
- Chỉ khởi tạo kết nối trước (preconnect) tới một hoặc hai nguồn (origin) thực sự cần thiết cho lần kết xuất đầu tiên. Độ tin cậy: 0.70
- Nhúng trực tiếp (inline) phần CSS quan trọng (critical CSS) cho khung nhìn đầu tiên và tải phần còn lại mà không chặn quá trình kết xuất. Độ tin cậy: 0.65
- Ảo hóa (virtualize) các danh sách dài thay vì kết xuất hàng nghìn nút DOM cùng lúc. Độ tin cậy: 0.80
- Chỉ tạo hoạt ảnh (animate) trên hai thuộc tính transform và opacity, đồng thời tôn trọng tùy chọn prefers-reduced-motion của người dùng. Độ tin cậy: 0.85
- Tránh gây tính toán lại bố cục liên tục (layout thrashing): gom nhóm các thao tác đọc DOM trước khi thực hiện các thao tác ghi DOM. Độ tin cậy: 0.75
- Kiểm thử trên điện thoại Android tầm trung với mạng bị bóp băng thông (throttled), chứ không chỉ thử nghiệm trên máy tính xách tay của lập trình viên. Độ tin cậy: 0.90
- Xem sự suy giảm hiệu năng (performance regression) giống như một lỗi phần mềm (bug), cần có người chịu trách nhiệm và ngày ấn định khắc phục. Độ tin cậy: 0.80
Web DevelopmentFrontendJavaScript+2
F@f
0
API GraphQL taste
Văn bản

Các quy ước mang tính định hướng rõ rệt để thiết kế, phát triển mở rộng và vận hành các API GraphQL.

# Gu thiết kế API GraphQL của một kỹ sư nền tảng (platform engineer)
- Thiết kế lược đồ (schema) xoay quanh các ca sử dụng (use cases) của phía client và ngôn ngữ nghiệp vụ (domain language), chứ không xoay quanh các bảng cơ sở dữ liệu. Độ tin cậy: 0.90
- Tự động sinh kiểu dữ liệu (types) cho cả phía server và client từ schema (sử dụng GraphQL Code Generator hoặc công cụ tương đương); không bao giờ tự viết tay các kiểu dữ liệu phản hồi. Độ tin cậy: 0.90
- Chỉ đặt các trường (fields) là non-null theo mặc định khi server luôn có thể phân giải (resolve) được chúng; giữ cho các trường phụ thuộc vào dịch vụ khác ở trạng thái có thể null (nullable) để một lỗi đơn lẻ không làm mất trắng toàn bộ phản hồi. Độ tin cậy: 0.80
- Sử dụng một đối tượng đầu vào duy nhất cho mỗi thao tác biến đổi dữ liệu (mutation) (ví dụ: createOrder(input: CreateOrderInput!)) và trả về một kiểu payload chuyên biệt, không trả về trực tiếp thực thể gốc (bare entity). Độ tin cậy: 0.85
- Trả về các lỗi nghiệp vụ dự kiến dưới dạng các thành phần kiểu hợp (typed union members) trong payload (ví dụ: OrderCreated | InsufficientStock); dành riêng mảng errors ở cấp cao nhất cho các lỗi hệ thống bất ngờ. Độ tin cậy: 0.75
- Phân trang danh sách bằng kết nối con trỏ theo phong cách Relay (edges, node, pageInfo) và giới hạn tham số first/last ở một mức tối đa hợp lý. Độ tin cậy: 0.80
- Gom nhóm (batch) và lưu đệm việc tải dữ liệu theo từng yêu cầu bằng DataLoader để triệt tiêu hoàn toàn vấn đề truy vấn N+1. Độ tin cậy: 0.95
- Áp dụng giới hạn độ sâu (depth) và độ phức tạp (complexity) của truy vấn ngay tại cổng giao tiếp (gateway). Độ tin cậy: 0.90
- Sử dụng truy vấn cố định (persisted queries - danh sách cho phép các mã băm thao tác) cho các ứng dụng client nội bộ (first-party) trên môi trường production. Độ tin cậy: 0.80
- Tắt tính năng tự tra cứu lược đồ (introspection) trên các điểm cuối (endpoints) production công khai trừ khi API đó thực sự được thiết kế để công khai cho bên thứ ba. Độ tin cậy: 0.70
- Phát triển mở rộng schema theo hướng bổ sung thêm (additively) và đánh dấu các trường cũ bằng chỉ thị @deprecated(reason: ...); không bao giờ phân tách phiên bản endpoint bằng đường dẫn như /v2. Độ tin cậy: 0.90
- Chỉ xóa bỏ một trường đã đánh dấu @deprecated sau khi số liệu đo lường mức sử dụng trường đó cho thấy không còn lưu lượng truy cập nào trong suốt một chu kỳ phát hành đầy đủ. Độ tin cậy: 0.85
- Chạy kiểm tra so sánh sự khác biệt của schema (schema diff) trong quy trình CI và cho dừng quá trình build nếu phát hiện các thay đổi gây phá vỡ tương thích (breaking changes). Độ tin cậy: 0.90
- Sử dụng các định danh ẩn (opaque IDs) duy nhất trên toàn cục và triển khai giao diện Node để hỗ trợ truy xuất lại dữ liệu (refetching). Độ tin cậy: 0.70
- Giữ cho các hàm phân giải (resolvers) thật mỏng gọn: chúng chỉ gọi các dịch vụ miền nghiệp vụ (domain services), và việc phân quyền (authorization) phải nằm ở tầng dịch vụ đó chứ không nằm trong các câu lệnh if của resolver. Độ tin cậy: 0.85
- Đặt tên rõ ràng cho mọi thao tác (operations) trong từng truy vấn của client để dữ liệu truy vết (traces), nhật ký (logs) và số liệu đo lường (metrics) dễ đọc hiểu. Độ tin cậy: 0.90
- Đặt các mảnh truy vấn (fragments) nằm cùng chỗ với chính các thành phần giao diện (UI components) sử dụng chúng. Độ tin cậy: 0.80
- Ưu tiên các kiểu vô hướng tùy chỉnh (custom scalars như DateTime, URL, Email) có kiểm tra tính hợp lệ hơn là dùng kiểu String thuần túy. Độ tin cậy: 0.75
- Chỉ sử dụng cơ chế đăng ký nhận dữ liệu (subscriptions) cho dữ liệu thời gian thực thực sự; cơ chế hỏi vòng định kỳ (polling) là hoàn toàn ổn đối với bất kỳ dữ liệu nào chỉ thay đổi sau mỗi vài phút. Độ tin cậy: 0.70
- Chỉ sử dụng kiến trúc liên hợp (federation) khi có nhiều nhóm khác nhau cùng sở hữu các đồ thị con (subgraphs) riêng biệt; một nhóm duy nhất thì chỉ nên dùng một schema duy nhất. Độ tin cậy: 0.75
- Truy vết mọi resolver bằng OpenTelemetry và thiết lập cảnh báo cho các trường xử lý chậm, chứ không chỉ cảnh báo các yêu cầu (requests) chậm nói chung. Độ tin cậy: 0.80
- Viết tài liệu cho mọi kiểu dữ liệu và mọi trường bằng phần mô tả trực tiếp trong schema; bản thân schema chính là tài liệu tham chiếu API. Độ tin cậy: 0.85
BackendWeb DevelopmentBest Practices+2
F@f
0
Khả Năng Tiếp Cận Web taste
Văn bản

Những ưu tiên mang tính định hướng rõ rệt để xây dựng giao diện web dễ tiếp cận, hoạt động hiệu quả cho người dùng bàn phím và trình đọc màn hình.

# Gu thiết kế Khả năng Tiếp cận Web (Web Accessibility) của một kỹ sư frontend
- Ưu tiên sử dụng HTML ngữ nghĩa trước tiên (button, a, nav, main, label); chỉ thêm ARIA khi không có phần tử gốc nào phù hợp. Độ tin cậy: 0.95
- Không bao giờ gắn trình xử lý sự kiện click (click handlers) vào thẻ div hoặc span; nếu nó hoạt động như một nút bấm, nó phải là thẻ button. Độ tin cậy: 0.95
- Liên kết (links) dùng để điều hướng và nút bấm (buttons) dùng để thực hiện hành động; đừng tráo đổi chúng chỉ vì lý do tạo kiểu giao diện (styling). Độ tin cậy: 0.90
- Mọi trường nhập liệu trong biểu mẫu (form input) đều phải có nhãn (label) hiển thị rõ ràng và được liên kết về mặt lập trình; văn bản gợi ý (placeholders) không phải là nhãn. Độ tin cậy: 0.95
- Giữ một thẻ h1 duy nhất trên mỗi trang và không bao giờ nhảy cóc các cấp tiêu đề (heading levels) chỉ để chỉnh kích thước hiển thị; hãy tạo kiểu bằng CSS để thay thế. Độ tin cậy: 0.80
- Viết văn bản thay thế (alt text) mô tả đúng mục đích của hình ảnh, và sử dụng alt="" cho các hình ảnh thuần túy mang tính trang trí. Độ tin cậy: 0.90
- Giữ chỉ báo tiêu điểm (focus indicator) hiển thị rõ ràng trên mọi phần tử tương tác; sử dụng :focus-visible thay vì xóa bỏ đường viền (outlines). Độ tin cậy: 0.95
- Mọi tính năng phải hoạt động được khi chỉ dùng riêng bàn phím, theo thứ tự phím Tab hợp lý và không có bẫy bàn phím (keyboard traps). Độ tin cậy: 0.95
- Tránh các giá trị tabindex dương; hãy sửa lại thứ tự DOM để thay thế. Độ tin cậy: 0.90
- Đạt chuẩn độ tương phản WCAG AA: tỷ lệ 4.5:1 đối với văn bản nội dung và 3:1 đối với văn bản cỡ lớn cùng các thành phần giao diện người dùng (UI). Độ tin cậy: 0.90
- Không bao giờ truyền tải ý nghĩa chỉ bằng màu sắc đơn thuần; hãy kết hợp màu sắc với văn bản, biểu tượng hoặc họa tiết. Độ tin cậy: 0.90
- Sử dụng phần tử dialog gốc (hoặc một thư viện đã được kiểm thử kỹ lưỡng) cho các hộp thoại modal, giữ tiêu điểm (trap focus) bên trong khi đang mở và trả tiêu điểm về phần tử kích hoạt khi đóng lại. Độ tin cậy: 0.85
- Thông báo trạng thái bất đồng bộ và các lỗi bằng vùng aria-live hoặc role="alert", đồng thời di chuyển tiêu điểm đến phần tóm tắt lỗi sau khi gửi biểu mẫu thất bại. Độ tin cậy: 0.80
- Tôn trọng tùy chọn prefers-reduced-motion và tránh tự động phát các hiệu ứng chuyển động kéo dài quá năm giây. Độ tin cậy: 0.85
- Thiết kế các vùng chạm (touch targets) có kích thước tối thiểu 24x24 CSS pixel, lý tưởng nhất là 44x44. Độ tin cậy: 0.75
- Đặt kích thước chữ bằng đơn vị rem và đảm bảo bố cục vẫn hiển thị tốt khi phóng to 200% và ở chiều rộng 320px mà không bị cuộn ngang. Độ tin cậy: 0.85
- Thêm liên kết "Chuyển đến nội dung chính" (Skip to main content) làm phần tử có thể nhận tiêu điểm (focusable) đầu tiên. Độ tin cậy: 0.80
- Thiết lập thuộc tính lang trên thẻ html và trên bất kỳ đoạn văn bản nội dòng nào được viết bằng ngôn ngữ khác. Độ tin cậy: 0.85
- Chạy các bài kiểm tra axe hoặc Lighthouse trong quy trình CI, đồng thời hiểu rõ rằng chúng chỉ phát hiện được khoảng một phần ba số vấn đề. Độ tin cậy: 0.85
- Kiểm thử thủ công các luồng quan trọng bằng trình đọc màn hình (VoiceOver và NVDA) trước mỗi đợt phát hành lớn. Độ tin cậy: 0.80
- Kiểm tra lỗi mã nguồn JSX bằng eslint-plugin-jsx-a11y và coi các lỗi của nó là lỗi dừng quá trình build (build failures). Độ tin cậy: 0.75
- Ưu tiên các thành phần giao diện nguyên thủy hỗ trợ tiếp cận (Radix, React Aria) hơn là tự xây dựng các hộp chọn (comboboxes) và menu từ đầu. Độ tin cậy: 0.80
Web DevelopmentBest PracticesFrontend+2
F@f
0
Trình Kiểm tra Robots.txt và Sitemap
Kỹ năng

Kiểm tra ngoại tuyến tệp robots.txt và XML sitemap của trang web theo đúng cách Google đọc chúng. Chọn nhóm trình thu thập dữ liệu (crawler), phân giải quy tắc Allow và Disallow có độ dài khớp lớn nhất (longest-match) kèm ký tự đại diện (wildcards), đồng thời kiểm thử các URL bắt buộc phải cho phép thu thập hoặc bị chặn. Cảnh báo lỗi chặn toàn bộ trang web, các URL trong sitemap bị robots.txt chặn, chặn tệp CSS và JS, các dòng noindex và crawl-delay không được hỗ trợ, cũng như các mục nhập sitemap nằm ngoài trang (off-site), trùng lặp hoặc không hợp lệ. Bao gồm một công cụ kiểm tra bằng thư viện chuẩn Python có hỗ trợ cờ --self-test.

---
name: robots-txt-and-sitemap-checker
description: Kiểm tra ngoại tuyến tệp robots.txt và XML sitemap của trang web theo đúng cách Google đọc chúng. Chọn nhóm trình thu thập dữ liệu (crawler), phân giải quy tắc Allow và Disallow có độ dài khớp lớn nhất (longest-match) kèm ký tự đại diện (wildcards), đồng thời kiểm thử các URL bắt buộc phải cho phép thu thập hoặc bị chặn. Cảnh báo lỗi chặn toàn bộ trang web, các URL trong sitemap bị robots.txt chặn, chặn tệp CSS và JS, các dòng noindex và crawl-delay không được hỗ trợ, cũng như các mục nhập sitemap nằm ngoài trang (off-site), trùng lặp hoặc không hợp lệ. Bao gồm một công cụ kiểm tra bằng thư viện chuẩn Python có hỗ trợ cờ --self-test.
---

# Trình Kiểm tra Robots.txt và Sitemap

Bạn giúp chủ sở hữu trang web, chuyên gia SEO và nhà phát triển web đảm bảo các công cụ tìm kiếm có thể thu thập dữ liệu (crawl) những trang quan trọng và bỏ qua những trang không cần thiết. Từ tệp robots.txt, XML sitemap và danh sách các URL quan trọng, bạn chỉ rõ quy tắc nào quyết định trạng thái của từng URL, tìm ra những mâu thuẫn giữa robots.txt và sitemap, giải thích từng vấn đề bằng ngôn ngữ dễ hiểu, đồng thời đề xuất bản robots.txt đã sửa lỗi cùng các chỉnh sửa cho sitemap.

## Các tệp trong kỹ năng này

- `scripts/crawl_check.py` - phân tích cú pháp robots.txt và một sitemap, quyết định trạng thái cho phép (allowed) hay chặn (blocked) trên từng URL giống hệt Googlebot, đồng thời in ra bảng URL cùng các phát hiện lỗi (chỉ dùng thư viện chuẩn của Python 3, không dùng mạng)
- `references/robots-txt-rules.md` - các nhóm (groups), quy tắc khớp dài nhất (longest-match) và ký tự đại diện (wildcard) kèm ví dụ, các chỉ thị được hỗ trợ, những sai lầm phổ biến và cách kiểm thử
- `references/sitemap-rules-and-fixes.md` - định dạng và giới hạn của sitemap, những gì nên liệt kê, thẻ lastmod, và cách khắc phục cho từng phát hiện lỗi
- `templates/crawl-audit-report.md` - mẫu báo cáo kiểm toán thu thập dữ liệu kèm bản đề xuất robots.txt để bàn giao cho nhà phát triển
- `examples/outdoor-shop-crawl-check.json` - một cửa hàng đồ dã ngoại sau khi ra mắt lại giao diện: phần blog và tệp JS vô tình bị chặn, có một dòng noindex, đường dẫn quản trị (admin) bị bỏ ngỏ, cùng một sitemap chứa các URL bị chặn, nằm ngoài tên miền (off-site), bị trùng lặp và ghi sai định dạng ngày tháng

## Quy trình làm việc

### 1. Thu thập các tệp
Hãy yêu cầu tệp robots.txt đang chạy thực tế (`https://<host>/robots.txt`), tệp sitemap hoặc sitemap index, địa chỉ gốc chuẩn tắc (canonical origin gồm giao thức scheme và tên miền host), cùng hai danh sách ngắn: các URL bắt buộc phải thu thập được (trang chủ, danh mục, sản phẩm hoặc bài viết hàng đầu, các gói CSS/JS) và các URL cần phải chặn (giỏ hàng, thanh toán, tìm kiếm nội bộ, bộ lọc, trang quản trị admin). Hỏi xem trình thu thập dữ liệu (crawler) nào là quan trọng nhất đối với họ (mặc định là Googlebot).

### 2. Chạy công cụ kiểm tra
```
python3 scripts/crawl_check.py examples/outdoor-shop-crawl-check.json --today 2026-10-23
python3 scripts/crawl_check.py --robots robots.txt --sitemap sitemap.xml --site https://www.example.com --allow /product/x --block /cart/
python3 scripts/crawl_check.py --self-test
```
Một tệp JSON đóng gói sẽ chứa thông tin trang web, tên crawler, các dòng trong robots.txt và sitemap cùng các danh sách URL. Mã thoát (exit code) bằng 1 nghĩa là có phát hiện lỗi mức CRITICAL hoặc HIGH, bằng 2 nghĩa là dữ liệu đầu vào không hợp lệ.

### 3. Giải thích và khắc phục
Bắt đầu với các lỗi mức CRITICAL và HIGH: chặn toàn bộ trang web, chặn các trang quan trọng, các URL có trong sitemap nhưng bị robots.txt chặn, các mục nhập trong sitemap trỏ ra ngoài tên miền (off-site). Với mỗi lỗi, hãy nêu rõ dòng quy tắc quyết định và đưa ra quy tắc đã sửa lại cho đúng. Giải thích rằng robots.txt không xóa các trang khỏi kết quả tìm kiếm và cũng không bảo vệ các trang riêng tư (`references/robots-txt-rules.md`), đồng thời chỉ dẫn tới bảng cách khắc phục trong `references/sitemap-rules-and-fixes.md`.

### 4. Bàn giao kết quả
Điền vào mẫu `templates/crawl-audit-report.md` với bảng kiểm tra URL, các phát hiện lỗi, toàn bộ nội dung tệp robots.txt hoàn chỉnh đã sửa và các thay đổi cần thiết cho bộ tạo sitemap (sitemap generator), sau đó chạy lại công cụ kiểm tra trên các tệp đã sửa để xác nhận.

## Ví dụ kết quả
Đối với tệp `examples/outdoor-shop-crawl-check.json`, trạng thái đánh giá là HIGH: chỉ thị `Disallow: /blog/` (dòng 9) đã chặn đường dẫn /blog/trail-running-shoes-review và trang /blog/spring-sale-recap đang được liệt kê trong sitemap, `Disallow: /assets/js/` chặn gói bundle của ứng dụng, và đường dẫn http://trailgear.example/backpacks/ trong sitemap bị tính là khác nguồn (off-site). Dòng `Noindex: /search/` bị Google bỏ qua, vì vậy /search/ và /admin/ vẫn có thể bị thu thập dữ liệu; ngoài ra sitemap còn có một đường dẫn /tents/ bị trùng lặp, một thẻ lastmod ghi năm 2027 và một thẻ ghi theo định dạng `18.10.2026`.

## Quy tắc

- Luôn nêu chính xác số dòng trong tệp robots.txt quyết định trạng thái của một URL, cùng với tên crawler đã được dùng để đánh giá.
- Tuyệt đối không bao giờ khuyên dùng robots.txt để ẩn các trang riêng tư hoặc nhạy cảm; hãy yêu cầu dùng cơ chế xác thực đăng nhập (authentication), và dùng thẻ `noindex` cho những trang cần loại khỏi kết quả tìm kiếm.
- Luôn cung cấp một tệp robots.txt hoàn chỉnh đã sửa lỗi chứ không chỉ đưa ra các đoạn trích rời rạc, đồng thời giữ nguyên các quy tắc mà chủ sở hữu trang web đã chủ đích thêm vào (ví dụ như chặn AhrefsBot).
- Công cụ kiểm tra không trực tiếp tải các URL qua mạng; hãy nói rõ rằng việc kiểm tra mã trạng thái HTTP (status codes), chuyển hướng (redirects), thẻ canonical và thẻ noindex cần phải thực hiện bằng trình thu thập thực tế hoặc qua Search Console.
FILE:references/robots-txt-rules.md
# Các quy tắc robots.txt (RFC 9309 và Google)

## Vị trí và bản chất
- Nằm ở thư mục gốc của từng tên miền (host) và giao thức (protocol): `https://www.example.com/robots.txt` chỉ có hiệu lực cho đúng tên miền và giao thức đó. Các biến thể `example.com`, `shop.example.com` và `http://` đều cần có tệp riêng của mình.
- Định dạng văn bản thuần túy (plain text), bảng mã UTF-8. Google chỉ đọc 500 KiB đầu tiên. Mã lỗi 404 đồng nghĩa với "cho phép thu thập mọi thứ"; mã lỗi máy chủ 5xx kéo dài có thể khiến Google ngừng thu thập dữ liệu toàn bộ trang web.
- Tệp robots.txt kiểm soát việc **thu thập dữ liệu (crawling)**, chứ không kiểm soát việc **lập chỉ mục (indexing)**. Một URL bị chặn vẫn có thể xuất hiện trên kết quả tìm kiếm (nhưng không có đoạn trích mô tả snippet) nếu có các trang khác đặt liên kết trỏ tới nó. Để giữ một trang không xuất hiện trên kết quả tìm kiếm, hãy cho phép thu thập dữ liệu và trả về chỉ thị `noindex`; để giữ trang ở chế độ riêng tư, hãy yêu cầu đăng nhập.

## Các nhóm (Groups)
```
User-agent: Googlebot
User-agent: Bingbot
Disallow: /tmp/

User-agent: *
Disallow: /cart/
```
- Các dòng `User-agent` nằm liền kề nhau sẽ dùng chung một nhóm. Các quy tắc bên dưới thuộc về nhóm ngay phía trên chúng.
- Một trình thu thập dữ liệu chỉ sử dụng **duy nhất một** nhóm: nhóm có tên khớp cụ thể nhất (`googlebot-image` được ưu tiên hơn `googlebot`), nếu không có mới dùng nhóm `*`. Các quy tắc từ nhóm `*` không được cộng gộp vào nhóm có tên cụ thể, vì vậy một lỗi rất phổ biến là tạo nhóm `Googlebot` riêng nhưng lại quên lặp lại các quy tắc chặn cơ bản đã viết trong nhóm `*`.
- Các nhóm có cùng tên sẽ được gộp lại với nhau.

## Cơ chế so khớp (Matching)
- Đường dẫn có phân biệt chữ hoa chữ thường (case-sensitive) và được so khớp tính từ đầu đường dẫn (bao gồm cả chuỗi truy vấn query string).
- Ký tự `*` khớp với bất kỳ chuỗi ký tự nào; ký tự `$` neo cố định điểm kết thúc của URL.
- Quy tắc khớp có **độ dài lớn nhất** (longest match) sẽ chiến thắng; nếu có độ dài bằng nhau thì chỉ thị `Allow` sẽ chiến thắng. Nếu không có quy tắc nào khớp thì mặc định là được phép thu thập (allowed).
- Chỉ thị `Disallow:` để trống giá trị đồng nghĩa với việc cho phép tất cả.

| Quy tắc | Chặn | Không chặn |
|---|---|---|
| `Disallow: /shop` | /shop, /shop/, /shopping | /Shop |
| `Disallow: /shop/` | /shop/shoes | /shop |
| `Disallow: /*?sort=` | /tents/?sort=price | /tents/?color=red&sort=price (cần dùng `/*sort=`) |
| `Disallow: /*.pdf$` | /docs/a.pdf | /docs/a.pdf?v=2 |
| `Disallow: /blog/` + `Allow: /blog/guides/` | /blog/news | /blog/guides/x |

## Các chỉ thị (Directives)
| Chỉ thị | Google hỗ trợ | Ghi chú |
|---|---|---|
| user-agent, allow, disallow | có | các quy tắc cốt lõi |
| sitemap | có | URL tuyệt đối, không giới hạn số lượng, đặt bên ngoài các nhóm |
| crawl-delay | không | Bing và Yandex có tuân thủ; đối với Google, hãy giảm tốc độ thu thập thông qua mã phản hồi từ máy chủ (429/503) |
| noindex, nofollow | không (đã ngừng hỗ trợ từ 2019) | hãy dùng thẻ `<meta name="robots" content="noindex">` hoặc tiêu đề HTTP `X-Robots-Tag` |
| host, clean-param | không | chỉ dành cho Yandex |

## Những sai lầm phổ biến
- Dòng `Disallow: /` còn sót lại từ môi trường thử nghiệm (staging) sau khi trang web chính thức ra mắt (khiến toàn bộ trang web biến mất khỏi luồng thu thập dữ liệu).
- Chặn các thư mục chứa CSS, JS hoặc hình ảnh: Google cần dựng hình (render) trang web và bắt buộc phải tải các tệp này; việc chặn tài nguyên có thể khiến trang web trông như bị lỗi giao diện hoặc bị đánh giá là không thân thiện với thiết bị di động.
- Chặn một trang đang gắn thẻ `noindex`: trình thu thập dữ liệu không bao giờ vào được trang để đọc thấy thẻ noindex, dẫn đến việc URL đó vẫn tiếp tục nằm lại trong chỉ mục tìm kiếm.
- Dùng robots.txt để giấu trang quản trị (admin) hoặc các trang riêng tư: tệp robots.txt hoàn toàn công khai và vô tình liệt kê chính xác vị trí của các trang nhạy cảm đó cho mọi người thấy.
- Để ngỏ các URL phân mảnh theo bộ lọc (sắp xếp, lọc thuộc tính, mã phiên session id), gây lãng phí ngân sách thu thập dữ liệu (crawl budget) vào vô số tổ hợp URL trùng lặp.
- Lỗi chính tả (ví dụ `Disalow`) sẽ bị lặng lẽ bỏ qua mà không có cảnh báo.

## Kiểm thử
- Google Search Console: báo cáo robots.txt (kiểm tra trạng thái tải tệp và các quy tắc đã phân tích) cùng công cụ Kiểm tra URL (URL Inspection - xem có bị chặn hay không).
- Trên máy cục bộ: `python3 scripts/crawl_check.py --robots robots.txt --site https://www.example.com --allow /product/x --block /cart/`.
FILE:references/sitemap-rules-and-fixes.md
# Các quy tắc và cách khắc phục lỗi XML Sitemap

## Định dạng
```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://www.example.com/tents/ultralight-2p</loc>
    <lastmod>2026-10-18T09:30:00+03:00</lastmod>
  </url>
</urlset>
```
- Tối đa **50,000 URLs** và dung lượng khi chưa nén tối đa **50 MB** trên mỗi tệp. Các trang web lớn hơn cần sử dụng tệp chỉ mục sơ đồ trang web (sitemap index dùng thẻ `<sitemapindex>` chứa các mục `<sitemap><loc>`), cho phép quản lý tối đa 50,000 tệp sitemap con.
- Bảng mã UTF-8, bắt buộc mã hóa thực thể (entity-escape) cho các ký tự `&`, `<`, `>`, `'`, `"` nằm bên trong thẻ `<loc>`.
- Giá trị trong `<loc>` phải là đường dẫn tuyệt đối, sử dụng cùng giao thức (protocol) và tên miền (host) với chính tệp sitemap đó (trừ khi được gửi chéo thông qua Search Console cho một tên miền đã xác minh quyền sở hữu).

## Những gì cần đưa vào danh sách
- Chỉ liệt kê các URL chuẩn tắc (canonical), có thể lập chỉ mục và trả về mã 200: không đưa vào các đường dẫn chuyển hướng (redirects), lỗi 404, các trang gắn `noindex`, các URL bị chặn bởi robots.txt, hoặc các URL trùng lặp do tham số.
- Phải khớp chính xác dạng thức chuẩn tắc: cùng giao thức (https), cùng tên miền (có www hoặc không có www), cùng quy ước dấu gạch chéo cuối câu (trailing-slash) giống hệt như trong thẻ `rel=canonical`.
- Mỗi URL chỉ xuất hiện duy nhất một lần.

## Thẻ lastmod
- Định dạng ngày giờ theo chuẩn W3C datetime: `2026-10-18`, `2026-10-18T09:30+03:00` hoặc `2026-10-18T06:30:00Z`. Các định dạng như `18.10.2026` hay `10/18/2026` đều không hợp lệ.
- Hãy đặt giá trị này theo thời điểm có thay đổi nội dung **thực sự có ý nghĩa** lần cuối cùng, chứ không phải thời điểm chạy tiến trình xuất bản (build time). Google chỉ sử dụng `lastmod` khi thấy giá trị này chính xác một cách nhất quán; việc để ngày tháng ở tương lai hoặc cập nhật lại ngày mới cho mọi URL mỗi ngày sẽ khiến Google học cách phớt lờ hoàn toàn trường thông tin này.
- Hai thẻ `<changefreq>` và `<priority>` bị Google bỏ qua hoàn toàn; chúng không gây hại gì nhưng không đáng tốn công duy trì.

## Cách thông báo sitemap cho các trình thu thập dữ liệu
- Khai báo dòng `Sitemap: https://www.example.com/sitemap.xml` trong tệp robots.txt (dùng URL tuyệt đối).
- Gửi trực tiếp trong Google Search Console và Bing Webmaster Tools, sau đó theo dõi số lượng trang "Đã phát hiện" (Discovered) và "Đã lập chỉ mục" (Indexed) cùng các thông báo lỗi.

## Cách khắc phục theo từng mã phát hiện lỗi
| Mã phát hiện | Cách khắc phục |
|---|---|
| SITEMAP_URL_BLOCKED | Hãy quyết định rõ: nếu trang đó cần được xếp hạng tìm kiếm, hãy gỡ bỏ dòng Disallow; nếu không cần, hãy xóa nó khỏi tệp sitemap |
| SITEMAP_OFF_SITE | Viết lại giá trị `<loc>` theo đúng giao thức và tên miền chuẩn tắc; kiểm tra lại thiết lập URL gốc (base URL) trong công cụ tạo sitemap |
| SITEMAP_DUPLICATE | Khử trùng lặp ngay trong công cụ tạo sitemap (thường do cùng một trang bị xuất ra ở cả danh sách danh mục và danh sách thẻ tag) |
| LASTMOD_INVALID / LASTMOD_FUTURE | Xuất định dạng chuẩn ISO 8601 lấy từ trường thời gian cập nhật thực tế của nội dung; kiểm tra lại múi giờ và đồng hồ của máy chủ |
| SITEMAP_TOO_BIG | Chia nhỏ theo chuyên mục hoặc theo mốc thời gian thành nhiều tệp con nằm dưới một tệp sitemap index duy nhất |
| SITEMAP_PARSE_ERROR | Mã hóa ký tự `&` trong các URL, xóa ký tự BOM ở đầu tệp hoặc khắc phục lỗi máy chủ trả về trang báo lỗi HTML thay vì tệp XML, kiểm tra lại khai báo namespace |

## Những yếu tố nằm ngoài phạm vi công cụ này
Tập lệnh hoạt động ngoại tuyến trên các tệp bạn cung cấp. Bạn cần kiểm tra thêm bằng trình thu thập dữ liệu thực tế hoặc Search Console đối với: mã trạng thái HTTP thực tế của các URL trong danh sách, chuỗi chuyển hướng (redirects), thẻ canonical, thẻ noindex và các liên kết ngôn ngữ thay thế hreflang.
FILE:templates/crawl-audit-report.md
# Báo cáo Kiểm toán Thu thập Dữ liệu (Crawl Audit) - <trang web> - <ngày>

**Đã kiểm tra:** robots.txt (<tải ngày / phiên bản>), <URL của sitemap>  **Trình thu thập:** <Googlebot>  **Người đánh giá:** <tên>

## Tóm tắt
- Trạng thái: <CRITICAL / HIGH / OK>
- robots.txt: <n> nhóm, <n> quy tắc áp dụng cho <crawler>
- Sitemap: <n> URL, <n> bị chặn, <n> khác tên miền (off-site), <n> bị trùng lặp

## Các URL quan trọng
| URL | Trạng thái mong muốn | Kết quả thực tế | Quy tắc quyết định | Cách khắc phục |
|---|---|---|---|---|
| | crawlable | | | |
| | blocked | | | |

## Các phát hiện lỗi và cách khắc phục
| Mức độ nghiêm trọng | Phát hiện | Chi tiết | Cách khắc phục | Người phụ trách |
|---|---|---|---|---|
| | | | | |

## Đề xuất tệp robots.txt mới
```
User-agent: *
Disallow: ...
Allow: ...

Sitemap: https://...
```

## Các bước theo dõi tiếp theo
- Triển khai tệp robots.txt và sitemap mới vào ngày <ngày>; kiểm thử lại trong Search Console (báo cáo robots.txt, công cụ Kiểm tra URL)
- Kiểm tra lại độ phủ chỉ mục (coverage) và thống kê thu thập dữ liệu (crawl stats) vào ngày <ngày>
FILE:examples/outdoor-shop-crawl-check.json
{
  "site": "https://www.trailgear.example",
  "user_agent": "Googlebot",
  "robots_txt": [
    "# robots.txt for www.trailgear.example (after the October relaunch)",
    "User-agent: *",
    "Disallow: /cart/",
    "Disallow: /checkout/",
    "Disallow: /account/",
    "Disallow: /*?sort=",
    "Disallow: /*?sessionid=",
    "Disallow: /assets/js/",
    "Disallow: /blog/",
    "Allow: /blog/guides/",
    "Crawl-delay: 10",
    "Noindex: /search/",
    "",
    "User-agent: AhrefsBot",
    "Disallow: /",
    "",
    "Sitemap: https://www.trailgear.example/sitemap.xml"
  ],
  "sitemap_xml": [
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
    "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
    "  <url><loc>https://www.trailgear.example/</loc><lastmod>2026-10-20</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/tents/</loc><lastmod>2026-10-18</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/tents/ultralight-2p</loc><lastmod>2026-10-18T09:30:00+03:00</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/tents/</loc><lastmod>2026-10-18</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/blog/spring-sale-recap</loc><lastmod>2026-04-02</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/blog/guides/layering-for-hiking</loc><lastmod>2026-09-12</lastmod></url>",
    "  <url><loc>http://trailgear.example/backpacks/</loc><lastmod>2026-10-01</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/sleeping-bags/</loc><lastmod>18.10.2026</lastmod></url>",
    "  <url><loc>https://www.trailgear.example/stoves/</loc><lastmod>2027-03-01</lastmod></url>",
    "</urlset>"
  ],
  "must_crawl": [
    "/tents/ultralight-2p",
    "/blog/trail-running-shoes-review",
    "/blog/guides/layering-for-hiking",
    "/assets/js/app.js",
    "/tents/?color=green"
  ],
  "must_block": [
    "/checkout/payment",
    "/tents/?sort=price",
    "/search/?q=stove",
    "/admin/"
  ]
}
FILE:scripts/crawl_check.py
#!/usr/bin/env python3
"""robots.txt and XML sitemap checker (Python 3 standard library only, works offline).

Input, either:
  a JSON bundle  {"site": "https://www.example.com", "user_agent": "Googlebot",
                  "robots_txt": [lines] or "text", "sitemap_xml": [lines] or "text",
                  "must_crawl": [urls or paths], "must_block": [urls or paths]}
  or files       --robots robots.txt [--sitemap sitemap.xml] --site https://... [--allow URL] [--block URL]
Matching follows RFC 9309 / Google: the group whose user-agent token matches the crawler name
(case-insensitive, most specific wins, else "*"), then the longest matching Allow/Disallow path
(* = any characters, $ = end of URL); on a tie Allow wins; no match = allowed.
Findings (severity):
  CRITICAL BLOCK_ALL               "Disallow: /" applies to the crawler with no Allow rules
  HIGH     MUST_CRAWL_BLOCKED      a URL that must be crawlable is disallowed
  HIGH     SITEMAP_URL_BLOCKED     a URL listed in the sitemap is disallowed
  HIGH     SITEMAP_PARSE_ERROR     sitemap is not well-formed XML or not urlset/sitemapindex
  HIGH     SITEMAP_TOO_BIG         more than 50,000 URLs in one sitemap
  HIGH     SITEMAP_OFF_SITE        a sitemap URL is on another host or scheme than the site
  MEDIUM   MUST_BLOCK_CRAWLABLE    a URL that should be blocked is crawlable (robots.txt is not access control)
  MEDIUM   ASSETS_BLOCKED          CSS, JS or image paths disallowed (pages cannot render for the crawler)
  MEDIUM   UNSUPPORTED_RULE        noindex / nofollow / host lines (ignored by Google)
  MEDIUM   SITEMAP_DUPLICATE       the same <loc> appears more than once
  MEDIUM   LASTMOD_FUTURE          <lastmod> after --today
  LOW      CRAWL_DELAY_IGNORED     crawl-delay (Googlebot ignores it; Bing and Yandex honour it)
  LOW      RULE_OUTSIDE_GROUP      allow/disallow before any user-agent line
  LOW      UNKNOWN_DIRECTIVE       unknown line or missing colon
  LOW      SITEMAP_RELATIVE        Sitemap: line is not an absolute URL
  LOW      NO_SITEMAP_LINE         robots.txt has no Sitemap: line
  LOW      LASTMOD_INVALID         <lastmod> is not W3C datetime (YYYY-MM-DD[Thh:mm[:ss]TZD])
Usage:
  python3 crawl_check.py bundle.json [--today 2026-10-23] [--json]
  python3 crawl_check.py --robots robots.txt --sitemap sitemap.xml --site https://www.example.com --allow /shop/
  python3 crawl_check.py --self-test
Exit code: 0 no CRITICAL/HIGH, 1 CRITICAL or HIGH findings, 2 bad input.
"""
import argparse
import datetime as dt
import json
import re
import sys
import urllib.parse
import xml.etree.ElementTree as ET

SEV_ORDER = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
KNOWN = {"user-agent", "allow", "disallow", "sitemap", "crawl-delay", "noindex", "nofollow", "host", "clean-param"}
ASSET_RE = re.compile(r"(\.css|\.js|\.png|\.jpe?g|\.webp|\.svg|\.gif|/static/|/assets/|/_next/|/wp-includes/|/css/|/js/)", re.I)
LASTMOD_RE = re.compile(r"^\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?)?)?$")
NS = "{http://www.sitemaps.org/schemas/sitemap/0.9}"


class DataError(ValueError):
    pass


def text_of(v):
    if v is None:
        return None
    return "\n".join(v) if isinstance(v, list) else str(v)


def parse_robots(text):
    """-> (groups [{agents, rules[(kind, path, line)]}], sitemaps [(url, line)], issues [(sev, code, msg)])"""
    groups, sitemaps, issues = [], [], []
    cur, last_was_agent = None, False
    for n, raw in enumerate(text.splitlines(), start=1):
        line = raw.split("#", 1)[0].strip()
        if not line:
            continue
        if ":" not in line:
            issues.append(("LOW", "UNKNOWN_DIRECTIVE", f"line {n}: no colon in {line!r}"))
            continue
        key, val = (x.strip() for x in line.split(":", 1))
        key = key.lower()
        if key == "user-agent":
            if not last_was_agent or cur is None:
                cur = {"agents": [], "rules": []}
                groups.append(cur)
            cur["agents"].append(val.lower())
            last_was_agent = True
            continue
        last_was_agent = False
        if key in ("allow", "disallow"):
            if cur is None:
                issues.append(("LOW", "RULE_OUTSIDE_GROUP", f"line {n}: {key} before any user-agent line is ignored"))
            elif val:
                cur["rules"].append((key, val, n))
        elif key == "sitemap":
            sitemaps.append((val, n))
            if not re.match(r"^https?://", val, re.I):
                issues.append(("LOW", "SITEMAP_RELATIVE", f"line {n}: Sitemap must be an absolute URL, got {val!r}"))
        elif key == "crawl-delay":
            issues.append(("LOW", "CRAWL_DELAY_IGNORED", f"line {n}: crawl-delay {val} is ignored by Googlebot"))
        elif key in ("noindex", "nofollow", "host", "clean-param"):
            issues.append(("MEDIUM", "UNSUPPORTED_RULE", f"line {n}: '{key}' in robots.txt is not supported by Google; use meta robots / X-Robots-Tag or redirects"))
        else:
            issues.append(("LOW", "UNKNOWN_DIRECTIVE", f"line {n}: unknown directive {key!r}"))
    return groups, sitemaps, issues


def pick_group(groups, agent):
    """Rules for the crawler: merge all groups with the longest matching agent token, else '*'."""
    a = agent.lower()
    best, rules = -1, []
    for g in groups:
        for tok in g["agents"]:
            if tok != "*" and tok and a.startswith(tok) and len(tok) > best:
                best, rules = len(tok), list(g["rules"])
            elif tok != "*" and tok and a.startswith(tok) and len(tok) == best:
                rules += g["rules"]
    if best >= 0:
        return rules
    return [r for g in groups if "*" in g["agents"] for r in g["rules"]]


def pattern_to_re(path):
    end = path.endswith("$")
    body = path[:-1] if end else path
    return re.compile("^" + ".*".join(re.escape(p) for p in body.split("*")) + ("$" if end else ""))


def path_of(url, site):
    u = urllib.parse.urlsplit(urllib.parse.urljoin(site + "/", url))
    return (u.path or "/") + (("?" + u.query) if u.query else "")


def decide(rules, path):
    """-> (allowed, matching rule or None)"""
    best = None
    for kind, pat, n in rules:
        if pattern_to_re(pat).match(path):
            key = (len(pat), kind == "allow")
            if best is None or key > best[0]:
                best = (key, (kind, pat, n))
    if best is None:
        return True, None
    return best[1][0] == "allow", best[1]


def parse_sitemap(text):
    try:
        root = ET.fromstring(text.strip())
    except ET.ParseError as e:
        raise DataError(f"sitemap XML error: {e}")
    tag = root.tag.replace(NS, "")
    if tag not in ("urlset", "sitemapindex"):
        raise DataError(f"sitemap root must be urlset or sitemapindex, got {tag!r}")
    child = "url" if tag == "urlset" else "sitemap"
    items = []
    for el in root.findall(NS + child) or root.findall(child):
        loc = el.findtext(NS + "loc") or el.findtext("loc") or ""
        lm = el.findtext(NS + "lastmod") or el.findtext("lastmod")
        items.append((loc.strip(), lm.strip() if lm else None))
    return tag, items


def check(bundle, today=None):
    site = (bundle.get("site") or "").rstrip("/")
    if not re.match(r"^https?://[^/]+$", site):
        raise DataError("site must be an origin like https://www.example.com")
    robots = text_of(bundle.get("robots_txt"))
    if robots is None:
        raise DataError("robots_txt is required")
    agent = bundle.get("user_agent") or "Googlebot"
    today = today or dt.date.today()
    groups, sitemaps, issues = parse_robots(robots)
    rules = pick_group(groups, agent)
    findings = [{"severity": s, "code": c, "message": m} for s, c, m in issues]

    def f(sev, code, msg):
        findings.append({"severity": sev, "code": code, "message": msg})

    if any(k == "disallow" and p == "/" for k, p, _ in rules) and not any(k == "allow" for k, _, _ in rules):
        f("CRITICAL", "BLOCK_ALL", f"'Disallow: /' blocks the whole site for {agent}")
    for k, p, n in rules:
        if k == "disallow" and ASSET_RE.search(p):
            f("MEDIUM", "ASSETS_BLOCKED", f"line {n}: Disallow {p} blocks CSS/JS/images needed to render pages")
    if not sitemaps:
        f("LOW", "NO_SITEMAP_LINE", "robots.txt has no Sitemap: line")
    url_checks = []
    for kind, key in (("crawl", "must_crawl"), ("block", "must_block")):
        for u in bundle.get(key) or []:
            p = path_of(u, site)
            allowed, rule = decide(rules, p)
            url_checks.append({"url": u, "path": p, "expect": kind, "allowed": allowed,
                               "rule": f"line {rule[2]}: {rule[0].title()}: {rule[1]}" if rule else "no rule"})
            if kind == "crawl" and not allowed:
                f("HIGH", "MUST_CRAWL_BLOCKED", f"{p} is blocked by line {rule[2]} ({rule[0].title()}: {rule[1]})")
            if kind == "block" and allowed:
                f("MEDIUM", "MUST_BLOCK_CRAWLABLE", f"{p} is crawlable ({'no rule' if not rule else 'line %d Allow: %s' % (rule[2], rule[1])}); protect private pages with auth or noindex, not robots.txt")
    sm_info = None
    sm_text = text_of(bundle.get("sitemap_xml"))
    if sm_text is not None:
        try:
            kind, items = parse_sitemap(sm_text)
        except DataError as e:
            f("HIGH", "SITEMAP_PARSE_ERROR", str(e))
            kind, items = None, []
        host = urllib.parse.urlsplit(site)
        seen, blocked, off = set(), 0, 0
        for loc, lm in items:
            u = urllib.parse.urlsplit(loc)
            if (u.scheme, u.netloc) != (host.scheme, host.netloc):
                off += 1
                f("HIGH", "SITEMAP_OFF_SITE", f"{loc} is not on {site}")
            if loc in seen:
                f("MEDIUM", "SITEMAP_DUPLICATE", f"{loc} listed more than once")
            seen.add(loc)
            if kind == "urlset":
                allowed, rule = decide(rules, path_of(loc, site))
                if not allowed:
                    blocked += 1
                    f("HIGH", "SITEMAP_URL_BLOCKED", f"{loc} is in the sitemap but blocked by line {rule[2]} ({rule[0].title()}: {rule[1]})")
            if lm is not None:
                if not LASTMOD_RE.match(lm):
                    f("LOW", "LASTMOD_INVALID", f"{loc}: lastmod {lm!r} is not W3C datetime")
                elif dt.date.fromisoformat(lm[:10] if len(lm) >= 10 else lm[:4] + "-01-01") > today:
                    f("MEDIUM", "LASTMOD_FUTURE", f"{loc}: lastmod {lm} is after {today}")
        if len(items) > 50000:
            f("HIGH", "SITEMAP_TOO_BIG", f"{len(items)} URLs; split into files of at most 50,000 URLs and use a sitemap index")
        sm_info = {"type": kind, "urls": len(items), "blocked": blocked, "off_site": off}
    findings.sort(key=lambda x: SEV_ORDER[x["severity"]])
    status = findings[0]["severity"] if findings and findings[0]["severity"] in ("CRITICAL", "HIGH") else "OK"
    return {"site": site, "user_agent": agent, "groups": len(groups), "rules_for_agent": len(rules),
            "sitemaps_declared": [s for s, _ in sitemaps], "sitemap": sm_info, "url_checks": url_checks,
            "findings": findings, "status": status}


def report(r):
    L = [f"# Crawl check for {r['site']} as {r['user_agent']}", "",
         f"robots.txt: {r['groups']} groups, {r['rules_for_agent']} rules apply to {r['user_agent']}; "
         f"Sitemap lines: {', '.join(r['sitemaps_declared']) or 'none'}"]
    if r["sitemap"]:
        s = r["sitemap"]
        L.append(f"Sitemap ({s['type']}): {s['urls']} URLs, {s['blocked']} blocked by robots.txt, {s['off_site']} off-site")
    if r["url_checks"]:
        L += ["", "| URL | Expected | Result | Deciding rule |", "|---|---|---|---|"]
        for c in r["url_checks"]:
            res = "allowed" if c["allowed"] else "blocked"
            good = (c["expect"] == "crawl") == c["allowed"]
            L.append(f"| {c['path']} | {c['expect']} | {res}{'' if good else ' (WRONG)'} | {c['rule']} |")
    L += ["", f"## Findings (status {r['status']})"]
    L += [f"- {x['severity']} {x['code']}: {x['message']}" for x in r["findings"]] or ["- none"]
    return "\n".join(L) + "\n"


def self_test():
    cases = []

    def ok(name, cond):
        cases.append(bool(cond))
        print(("PASS " if cond else "FAIL ") + name)

    def codes(r):
        return {x["code"] for x in r["findings"]}

    R = lambda t: pick_group(parse_robots(t)[0], "Googlebot")
    rules = R("User-agent: *\nDisallow: /shop/\nAllow: /shop/products/\n")
    ok("longest match wins (allow deeper path)", decide(rules, "/shop/products/a")[0] and not decide(rules, "/shop/cart")[0])
    ok("tie goes to allow", decide(R("User-agent: *\nDisallow: /p\nAllow: /p\n"), "/p")[0])
    ok("no rule = allowed", decide(rules, "/blog/")[0])
    wild = R("User-agent: *\nDisallow: /*?sort=\nDisallow: /*.pdf$\n")
    ok("* wildcard matches query", not decide(wild, "/shop/shoes?sort=price")[0])
    ok("$ anchors end", not decide(wild, "/files/a.pdf")[0] and decide(wild, "/files/a.pdf?x=1")[0])
    ok("empty Disallow allows everything", decide(R("User-agent: *\nDisallow:\n"), "/x")[0])
    spec = parse_robots("User-agent: *\nDisallow: /\n\nUser-agent: Googlebot\nDisallow: /private/\n")[0]
    ok("specific group beats *", decide(pick_group(spec, "Googlebot"), "/shop")[0] and not decide(pick_group(spec, "Bingbot"), "/shop")[0])
    ok("googlebot-image uses longest token", pick_group(parse_robots("User-agent: googlebot\nDisallow: /a\nUser-agent: googlebot-image\nDisallow: /b\n")[0], "Googlebot-Image")[0][1] == "/b")
    multi = parse_robots("User-agent: a\nUser-agent: b\nDisallow: /x\n")[0]
    ok("consecutive user-agents share a group", len(multi) == 1 and multi[0]["agents"] == ["a", "b"])
    ok("comments stripped", R("User-agent: * # all\nDisallow: /tmp/ # temp\n")[0][1] == "/tmp/")
    base = {"site": "https://www.shop.test", "robots_txt": "User-agent: *\nDisallow: /\n"}
    r = check(base, dt.date(2026, 10, 23))
    ok("block all is CRITICAL", "BLOCK_ALL" in codes(r) and r["status"] == "CRITICAL")
    r = check({**base, "robots_txt": ["User-agent: *", "Disallow: /checkout/", "Sitemap: https://www.shop.test/sitemap.xml"],
               "must_crawl": ["/checkout/thanks"], "must_block": ["/admin/"]}, dt.date(2026, 10, 23))
    ok("must_crawl blocked is HIGH", "MUST_CRAWL_BLOCKED" in codes(r) and r["status"] == "HIGH")
    ok("must_block crawlable is MEDIUM", "MUST_BLOCK_CRAWLABLE" in codes(r))
    ok("list of lines accepted, sitemap line read", r["sitemaps_declared"] == ["https://www.shop.test/sitemap.xml"])
    r = check({**base, "robots_txt": "Disallow: /x\nUser-agent: *\nNoindex: /tmp/\nCrawl-delay: 5\nDisalow: /y\nSitemap: /sitemap.xml\nDisallow: /assets/\n"}, dt.date(2026, 10, 23))
    for c in ("RULE_OUTSIDE_GROUP", "UNSUPPORTED_RULE", "CRAWL_DELAY_IGNORED", "UNKNOWN_DIRECTIVE", "SITEMAP_RELATIVE", "ASSETS_BLOCKED"):
        ok(c + " flagged", c in codes(r))
    ok("missing sitemap line flagged", "NO_SITEMAP_LINE" in codes(check({**base, "robots_txt": "User-agent: *\nDisallow:\n"})))
    sm = ('<?xml version="1.0"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
          "<url><loc>https://www.shop.test/a</loc><lastmod>2026-10-01</lastmod></url>"
          "<url><loc>https://www.shop.test/a</loc></url>"
          "<url><loc>http://www.shop.test/b</loc><lastmod>01/10/2026</lastmod></url>"
          "<url><loc>https://www.shop.test/cart/x</loc><lastmod>2030-01-01T00:00:00+00:00</lastmod></url></urlset>")
    r = check({**base, "robots_txt": "User-agent: *\nDisallow: /cart/\nSitemap: https://www.shop.test/sitemap.xml\n", "sitemap_xml": sm}, dt.date(2026, 10, 23))
    for c in ("SITEMAP_DUPLICATE", "SITEMAP_OFF_SITE", "LASTMOD_INVALID", "LASTMOD_FUTURE", "SITEMAP_URL_BLOCKED"):
        ok(c + " flagged", c in codes(r))
    ok("sitemap counts", r["sitemap"] == {"type": "urlset", "urls": 4, "blocked": 1, "off_site": 1})
    ok("bad XML is HIGH SITEMAP_PARSE_ERROR", "SITEMAP_PARSE_ERROR" in codes(check({**base, "robots_txt": "", "sitemap_xml": "<urlset><url>"})))
    idx = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><sitemap><loc>https://www.shop.test/s1.xml</loc></sitemap></sitemapindex>'
    ok("sitemap index read", check({**base, "robots_txt": "", "sitemap_xml": idx})["sitemap"]["type"] == "sitemapindex")
    for bad, label in (({"robots_txt": ""}, "missing site"), ({"site": "https://x.test/path", "robots_txt": ""}, "site with path"),
                       ({"site": "https://x.test"}, "missing robots_txt")):
        try:
            check(bad)
            ok(label + " rejected", False)
        except DataError:
            ok(label + " rejected", True)
    print(f"{sum(cases)}/{len(cases)} passed")
    return 0 if all(cases) else 1


def main(argv=None):
    ap = argparse.ArgumentParser(description="Check robots.txt rules and an XML sitemap offline.")
    ap.add_argument("bundle", nargs="?", help="JSON bundle (see module docstring)")
    ap.add_argument("--robots", help="robots.txt file (instead of a bundle)")
    ap.add_argument("--sitemap", help="sitemap.xml file")
    ap.add_argument("--site", help="site origin, e.g. https://www.example.com")
    ap.add_argument("--agent", help="crawler name (default Googlebot)")
    ap.add_argument("--allow", action="append", default=[], help="URL or path that must be crawlable (repeatable)")
    ap.add_argument("--block", action="append", default=[], help="URL or path that should be blocked (repeatable)")
    ap.add_argument("--today", help="date for the future-lastmod check (default today)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test()
    try:
        if a.bundle:
            b = json.load(open(a.bundle, encoding="utf-8"))
        elif a.robots:
            b = {"site": a.site, "robots_txt": open(a.robots, encoding="utf-8").read(),
                 "sitemap_xml": open(a.sitemap, encoding="utf-8").read() if a.sitemap else None,
                 "must_crawl": a.allow, "must_block": a.block}
        else:
            ap.error("give a JSON bundle, --robots FILE or --self-test")
        if a.agent:
            b["user_agent"] = a.agent
        today = dt.date.fromisoformat(a.today) if a.today else None
        r = check(b, today)
    except (OSError, ValueError, DataError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(r, indent=2) if a.json else report(r), end="\n" if a.json else "")
    return 1 if r["status"] in ("CRITICAL", "HIGH") else 0


if __name__ == "__main__":
    sys.exit(main())
Web DevelopmentPythonSEO+2
F@f
0
Ứng dụng Web Django taste
Văn bản

Các quy ước định hướng rõ ràng để xây dựng và vận hành ứng dụng web Django trên môi trường thực tế (production).

# Ứng dụng Web Django taste của một kỹ sư backend Python
- Bắt đầu mọi dự án với một mô hình người dùng tùy chỉnh (AbstractUser) ngay cả khi chưa cần thêm trường dữ liệu mới nào. Độ tin cậy: 0.95
- Tách biệt cấu hình (settings) theo từng môi trường và đọc các khóa bí mật cũng như URL từ biến môi trường bằng django-environ hoặc thư viện tương tự. Độ tin cậy: 0.90
- Giữ cho các app nhỏ gọn và tập trung vào miền nghiệp vụ; một app phải import tới một nửa dự án là dấu hiệu cho thấy nó nên được tách nhỏ hoặc gộp lại. Độ tin cậy: 0.75
- Đặt logic nghiệp vụ trong các hàm dịch vụ (service functions) thuần túy hoặc phương thức của model, không đặt trong views, serializers hay signals. Độ tin cậy: 0.85
- Tránh sử dụng signals cho các luồng công việc cốt lõi; hãy gọi tường minh tác vụ phụ (side effect) để luồng xử lý dễ đọc và dễ kiểm thử. Độ tin cậy: 0.85
- Chủ động sử dụng select_related và prefetch_related, đồng thời kiểm tra số lượng truy vấn bằng django-debug-toolbar hoặc assertNumQueries. Độ tin cậy: 0.90
- Thêm các ràng buộc cơ sở dữ liệu (UniqueConstraint, CheckConstraint) thay vì chỉ dựa vào việc xác thực trên form hoặc serializer. Độ tin cậy: 0.90
- Xem xét kỹ mọi tệp migration được tạo ra trước khi commit và tuyệt đối không chỉnh sửa một migration đã chạy trên môi trường production. Độ tin cậy: 0.90
- Viết các data migration bằng RunPython sử dụng apps.get_model, tuyệt đối không import trực tiếp các model. Độ tin cậy: 0.85
- Ưu tiên sử dụng class-based generic views cho các thao tác CRUD và function views cho bất kỳ xử lý nào có luồng điều khiển đặc biệt. Độ tin cậy: 0.70
- Sử dụng Django REST Framework hoặc django-ninja cho các API, với serializers hoặc schemas được khai báo tường minh thay vì dùng fields = "__all__". Độ tin cậy: 0.85
- Bọc các thao tác ghi nhiều bước trong transaction.atomic và đưa các tác vụ chạy nền vào hàng đợi bằng transaction.on_commit. Độ tin cậy: 0.90
- Chạy các tác vụ chậm hoặc gọi dịch vụ bên ngoài trong Celery hoặc hàng đợi tác vụ khác, với các tác vụ có tính lũy đẳng (idempotent) và cơ chế thử lại (retry) rõ ràng. Độ tin cậy: 0.85
- Viết kiểm thử bằng pytest-django và factory_boy thay vì lưu các tệp fixture dưới dạng JSON trong kho mã nguồn. Độ tin cậy: 0.85
- Giữ trang quản trị (admin) cho các thao tác vận hành nội bộ; tùy chỉnh list_display, search_fields và list_select_related để trang luôn phản hồi nhanh. Độ tin cậy: 0.80
- Phục vụ các tệp tĩnh bằng WhiteNoise hoặc CDN và lưu trữ tệp do người dùng tải lên trên dịch vụ lưu trữ đối tượng (object storage), tuyệt đối không lưu trên ổ đĩa của máy chủ ứng dụng. Độ tin cậy: 0.85
- Chạy ứng dụng phía sau gunicorn hoặc uvicorn với DEBUG=False, thiết lập ALLOWED_HOSTS và đảm bảo lệnh python manage.py check --deploy vượt qua kiểm tra trong CI. Độ tin cậy: 0.95
- Sử dụng HTMX kết hợp với các template kết xuất phía máy chủ (server-rendered) trước khi nghĩ đến việc tách riêng một ứng dụng SPA. Độ tin cậy: 0.65
- Cố định phiên bản các thư viện phụ thuộc bằng tệp lock và nâng cấp các bản phát hành Django LTS theo lịch trình định kỳ. Độ tin cậy: 0.80
- Kiểm tra lỗi cú pháp (lint) và định dạng mã bằng Ruff, đồng thời kiểm tra kiểu dữ liệu bằng mypy kết hợp với django-stubs. Độ tin cậy: 0.75
BackendWeb DevelopmentBest Practices+1
F@f
0
Ứng dụng SvelteKit taste
Văn bản

Các quy ước giàu tính định hướng để xây dựng ứng dụng SvelteKit full-stack với Svelte 5 runes và TypeScript.

# Gu thiết kế (taste) Ứng dụng SvelteKit của một lập trình viên web full-stack
- Bắt đầu mọi dự án bằng khuôn mẫu (template) chính thức `sv create`, bật sẵn TypeScript và kích hoạt chế độ nghiêm ngặt (strict mode). Độ tin cậy: 0.90
- Sử dụng Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`) cho tất cả các component mới thay vì các câu lệnh phản ứng (reactive statements) kiểu cũ. Độ tin cậy: 0.90
- Tìm đến `$derived` trước khi nghĩ tới `$effect`, và chỉ giữ `$effect` cho việc đồng bộ hóa với những thứ nằm ngoài Svelte như DOM hoặc các thư viện bên thứ ba. Độ tin cậy: 0.90
- Tải dữ liệu trong `+page.server.ts` khi cần dùng tới thông tin bí mật (secrets) hoặc cơ sở dữ liệu, và chỉ sử dụng `+page.ts` dùng chung (universal) cho các lệnh gọi lấy dữ liệu công khai. Độ tin cậy: 0.90
- Xử lý các thao tác thay đổi dữ liệu (mutations) bằng form actions và `use:enhance` để biểu mẫu vẫn hoạt động được khi không có JavaScript và tự động hưởng lợi từ khả năng nâng cấp lũy tiến (progressive enhancement). Độ tin cậy: 0.85
- Xác thực dữ liệu đầu vào của biểu mẫu và API ở phía máy chủ bằng Zod hoặc Valibot, đồng thời trả về `fail()` kèm lỗi chi tiết từng trường thay vì ném ngoại lệ (throw). Độ tin cậy: 0.90
- Giữ mã nguồn chỉ chạy trên máy chủ trong thư mục `$lib/server` để quá trình build lập tức báo lỗi nếu mã đó vô tình bị import vào phía máy khách. Độ tin cậy: 0.95
- Đọc các thông tin bí mật từ `$env/static/private` hoặc `$env/dynamic/private`, tuyệt đối không đọc từ các module biến môi trường công khai. Độ tin cậy: 0.95
- Đặt logic xác thực và tra cứu phiên làm việc (session) trong `hooks.server.ts` và cung cấp thông tin người dùng thông qua `event.locals`. Độ tin cậy: 0.90
- Tuyệt đối không lưu trữ dữ liệu người dùng của từng yêu cầu (per-request) vào biến trạng thái ở cấp độ module, vì nó sẽ làm rò rỉ dữ liệu giữa các yêu cầu khác nhau trên máy chủ. Độ tin cậy: 0.95
- Chia sẻ trạng thái phía máy khách thông qua context hoặc các module nhỏ dựa trên rune trong các file `.svelte.ts`, thay vì dùng một store toàn cục cho mọi thứ. Độ tin cậy: 0.80
- Sử dụng các nhóm route như `(app)` và `(marketing)` để áp dụng các bố cục (layouts) khác nhau cho từng phân khu mà không làm thay đổi đường dẫn URL. Độ tin cậy: 0.80
- Kết xuất trước (prerender) các trang tĩnh như trang tài liệu và tiếp thị, đồng thời giữ các trang động được kết xuất từ phía máy chủ (server-rendered). Độ tin cậy: 0.85
- Chọn một bộ chuyển đổi (adapter) tường minh (`adapter-node`, `adapter-vercel`, `adapter-cloudflare`) thay vì phụ thuộc vào `adapter-auto` trên môi trường production. Độ tin cậy: 0.80
- Trả về dữ liệu có kiểu rõ ràng từ các hàm `load` và dựa vào `./$types` được sinh tự động thay vì tự viết kiểu cho prop bằng tay. Độ tin cậy: 0.90
- Sử dụng các trang `+error.svelte` và hàm trợ giúp `error()` cho những lỗi nằm trong dự kiến, và ghi nhật ký các lỗi bất ngờ trong `handleError`. Độ tin cậy: 0.85
- Định kiểu giao diện bằng CSS có phạm vi cục bộ theo component (scoped component CSS) làm mặc định và chỉ thêm Tailwind khi nhóm phát triển đã quen sử dụng nó. Độ tin cậy: 0.70
- Giữ cho các component nhỏ gọn và truyền các `snippets` cho phần nội dung linh hoạt thay vì dùng quá nhiều prop kiểu boolean. Độ tin cậy: 0.80
- Kiểm thử các component bằng Vitest và `@testing-library/svelte`, đồng thời bao phủ các luồng người dùng chính bằng Playwright. Độ tin cậy: 0.85
- Chạy `svelte-check`, ESLint và Prettier kèm plugin Svelte trong quy trình CI trên mọi pull request. Độ tin cậy: 0.85
Web DevelopmentBest PracticesFrontend+1
F@f
0
Xác thực người dùng taste
Văn bản

Các quy ước mang tính định hướng rõ ràng để xây dựng luồng đăng nhập, phiên làm việc, xác thực đa yếu tố (MFA) và OAuth bảo mật trong các ứng dụng web.

# Gu xây dựng hệ thống Xác thực (Authentication) của một kỹ sư backend chú trọng bảo mật
- Sử dụng một thư viện hoặc nhà cung cấp dịch vụ xác thực được bảo trì tốt thay vì tự viết thủ công luồng đăng nhập, quản lý phiên và xử lý token. Độ tin cậy: 0.95
- Băm (hash) mật khẩu bằng Argon2id, hoặc bcrypt với hệ số chi phí (cost) tối thiểu là 12, và không bao giờ dùng hàm băm nhanh thông thường như SHA-256. Độ tin cậy: 0.95
- Ưu tiên phiên làm việc phía máy chủ (server-side sessions) lưu trong cookie có cờ `HttpOnly`, `Secure`, `SameSite=Lax` cho các ứng dụng web bên thứ nhất (first-party) thay vì lưu JWT trong `localStorage`. Độ tin cậy: 0.90
- Sử dụng access token có thời hạn ngắn kết hợp với refresh token xoay vòng (rotating refresh tokens) khi bạn thực sự cần token phi trạng thái (stateless) cho các API hoặc ứng dụng di động. Độ tin cậy: 0.85
- Phát hiện hành vi tái sử dụng refresh token và thu hồi (revoke) toàn bộ họ token (token family) ngay khi điều đó xảy ra. Độ tin cậy: 0.85
- Tạo lại (regenerate) ID phiên làm việc khi đăng nhập, đăng xuất và khi thay đổi quyền hạn để ngăn chặn tấn công ấn định phiên (session fixation). Độ tin cậy: 0.95
- Cung cấp khóa truy cập (passkeys / WebAuthn) như một phương thức đăng nhập hạng nhất (first-class), chứ không chỉ làm yếu tố xác thực thứ hai. Độ tin cậy: 0.80
- Hỗ trợ TOTP hoặc passkey cho xác thực đa yếu tố (MFA) và chỉ coi mã SMS là phương án dự phòng cuối cùng. Độ tin cậy: 0.85
- Giới hạn tần suất (rate limit) đăng nhập, đăng ký và đặt lại mật khẩu theo từng tài khoản và từng địa chỉ IP, sử dụng thời gian chờ tăng dần theo cấp số nhân (exponential backoff) thay vì khóa cứng tài khoản vĩnh viễn. Độ tin cậy: 0.90
- Trả về cùng một thông báo chung cho cả trường hợp email không tồn tại và sai mật khẩu để kẻ tấn công không thể dò quét danh sách tài khoản (account enumeration). Độ tin cậy: 0.90
- Đảm bảo các token đặt lại mật khẩu và magic link chỉ sử dụng một lần, sinh ngẫu nhiên, được băm khi lưu trữ (hashed at rest) và hết hạn trong vòng 15 đến 60 phút. Độ tin cậy: 0.95
- Kiểm tra mật khẩu đối chiếu với danh sách các mật khẩu đã bị lộ lọt và loại bỏ các quy tắc đặt mật khẩu tùy tiện như "bắt buộc phải có một ký tự đặc biệt". Độ tin cậy: 0.85
- Sử dụng luồng OAuth 2.1 Authorization Code kết hợp PKCE cho mọi ứng dụng khách (client), và không bao giờ sử dụng luồng implicit (implicit flow). Độ tin cậy: 0.95
- Xác thực tham số `state` và `nonce` trên mọi lệnh gọi lại (callback) của OAuth và OIDC. Độ tin cậy: 0.95
- Chỉ liên kết đăng nhập mạng xã hội (social logins) với các tài khoản hiện có sau khi nhà cung cấp trả về một địa chỉ email đã được xác minh. Độ tin cậy: 0.90
- Tách biệt rõ ràng giữa xác thực (authentication - bạn là ai) và phân quyền (authorization - bạn được làm gì), đồng thời kiểm tra quyền hạn trên máy chủ đối với mọi yêu cầu (request). Độ tin cậy: 0.95
- Yêu cầu nhập lại thông tin đăng nhập hoặc xác thực lại bằng passkey trước khi thực hiện các hành động nhạy cảm như thay đổi email, mật khẩu hoặc thông tin nhận thanh toán. Độ tin cậy: 0.85
- Thông báo cho người dùng qua email khi có lượt đăng nhập từ thiết bị mới, khi thay đổi mật khẩu và khi thay đổi cài đặt MFA. Độ tin cậy: 0.80
- Cho phép người dùng xem và thu hồi các phiên làm việc đang hoạt động của họ ngay trong phần cài đặt tài khoản. Độ tin cậy: 0.80
- Ghi nhật ký (log) các sự kiện xác thực kèm theo ID người dùng, địa chỉ IP và user agent, nhưng không bao giờ ghi log mật khẩu, token hoặc đường dẫn đặt lại mật khẩu. Độ tin cậy: 0.95
- Bảo vệ các yêu cầu làm thay đổi trạng thái dựa trên cookie bằng cookie `SameSite` kết hợp với CSRF token hoặc kiểm tra header `Origin`. Độ tin cậy: 0.90
- Lưu trữ các khóa ký (signing keys) trong trình quản lý bí mật (secrets manager) và hỗ trợ xoay vòng khóa (key rotation) với header `kid` ngay từ ngày đầu tiên. Độ tin cậy: 0.85
- Viết các bài kiểm thử tích hợp (integration tests) cho các kịch bản lỗi (unhappy paths): token hết hạn, phiên bị thu hồi, đường dẫn bị phát lại (replayed links) và sai định danh tổ chức (wrong tenants). Độ tin cậy: 0.85
BackendWeb DevelopmentBest Practices+1
F@f
0
Ứng dụng Rails taste
Văn bản

Các quy ước mang tính định hướng rõ ràng để xây dựng ứng dụng Ruby on Rails dễ bảo trì với Hotwire và các thiết lập mặc định hiện đại của Rails.

# Gu phát triển Ứng dụng Rails của một lập trình viên Ruby on Rails thực dụng
- Bắt đầu các ứng dụng mới trên phiên bản Rails mới nhất cùng với PostgreSQL, và gắn bó với các thiết lập mặc định trừ khi có lý do rõ ràng để thay thế một thành phần nào đó. Độ tin cậy: 0.90
- Giữ cho các controller mỏng (thin) và tuân thủ chuẩn RESTful, thêm các tài nguyên mới như `SessionsController` hoặc `ArchivesController` thay vì tạo các action tùy chỉnh. Độ tin cậy: 0.90
- Đặt logic nghiệp vụ (domain logic) trong các model và các đối tượng Ruby thuần túy (PORO) dưới thư mục `app/models`, và chỉ dùng đến các service object khi một luồng công việc trải rộng qua nhiều model. Độ tin cậy: 0.75
- Sử dụng `concerns` một cách tiết chế và chỉ dành cho những hành vi thực sự được chia sẻ chung, chứ không phải để che giấu một model phình to. Độ tin cậy: 0.75
- Ưu tiên dùng Hotwire với Turbo và Stimulus cho các tính năng tương tác trước khi quyết định thêm một ứng dụng JavaScript SPA tách biệt. Độ tin cậy: 0.85
- Xác thực (validate) dữ liệu trong model và củng cố thêm bằng các ràng buộc cơ sở dữ liệu như `NOT NULL`, chỉ mục duy nhất (unique indexes) và khóa ngoại (foreign keys). Độ tin cậy: 0.95
- Viết các bản di chuyển dữ liệu (migrations) có khả năng đảo ngược (reversible), thêm chỉ mục đồng thời (`concurrently`) trên các bảng lớn, và không bao giờ chỉnh sửa một migration đã chạy trên môi trường production. Độ tin cậy: 0.90
- Giữ các callback cho việc chuẩn hóa dữ liệu đơn giản và chuyển các tác vụ phụ (side effects) như gửi email và gọi API vào các công việc chạy nền (jobs) tường minh. Độ tin cậy: 0.85
- Chạy các tác vụ nền bằng Active Job trên Solid Queue hoặc Sidekiq, và giữ cho mọi job có tính lũy đẳng (idempotent) và an toàn khi thử lại (retry). Độ tin cậy: 0.90
- Truyền ID của bản ghi vào các job thay vì truyền toàn bộ đối tượng, và nạp lại (reload) bản ghi đó bên trong job. Độ tin cậy: 0.85
- Phát hiện các truy vấn N+1 bằng `strict_loading` hoặc gem Bullet, và khắc phục chúng bằng `includes` hoặc `preload`. Độ tin cậy: 0.90
- Sử dụng strong parameters ở mọi nơi và không bao giờ truyền trực tiếp `params` vào hàm `update` hoặc `create`. Độ tin cậy: 0.95
- Lưu trữ thông tin bí mật (secrets) trong chứng chỉ mã hóa (encrypted credentials) hoặc biến môi trường, không bao giờ lưu trong các tệp YAML được commit lên kho mã nguồn. Độ tin cậy: 0.95
- Kiểm thử bằng Minitest hoặc RSpec, tập trung vào kiểm thử model và request, kết hợp với một vài kiểm thử hệ thống (system tests) cho các luồng quan trọng. Độ tin cậy: 0.80
- Sử dụng fixtures hoặc các factory gọn nhẹ, tránh tạo các chuỗi factory lồng nhau quá sâu làm khởi tạo tới nửa cơ sở dữ liệu cho mỗi bài kiểm thử. Độ tin cậy: 0.75
- Kiểm tra quy chuẩn mã nguồn (lint) bằng RuboCop sử dụng cấu hình `rubocop-rails-omakase` hoặc một bộ quy tắc nhỏ đã thống nhất, được thực thi bắt buộc trong CI. Độ tin cậy: 0.80
- Quét các lỗ hổng bảo mật bằng Brakeman và `bundler-audit` trên mỗi pull request. Độ tin cậy: 0.90
- Lưu vào bộ nhớ đệm (cache) các giao diện (views) tốn nhiều tài nguyên bằng kỹ thuật fragment caching và khóa cache dựa trên `updated_at`, sử dụng Solid Cache hoặc Redis. Độ tin cậy: 0.80
- Sử dụng các scope và đối tượng truy vấn (query objects) cho các truy vấn tái sử dụng thay vì viết các chuỗi phương thức dài lặp đi lặp lại khắp các controller. Độ tin cậy: 0.80
- Giữ số lượng gem ít và được bảo trì tốt, đồng thời nâng cấp Rails từng phiên bản phụ (minor version) một sau khi đã sửa hết các cảnh báo ngừng hỗ trợ (deprecation warnings). Độ tin cậy: 0.85
- Triển khai bằng Kamal hoặc một nền tảng được quản lý (managed platform) sử dụng Docker image được đóng gói trong CI, và chạy migrations như một bước phát hành (release step) tách biệt. Độ tin cậy: 0.80
- Sử dụng tính năng mã hóa của Active Record (Active Record encryption) cho các cột dữ liệu nhạy cảm như token và dữ liệu cá nhân. Độ tin cậy: 0.80
BackendWeb DevelopmentBest Practices
F@f
0
Ứng dụng Laravel taste
Văn bản

Các quy ước thực tế để xây dựng ứng dụng Laravel dễ bảo trì với các thiết lập mặc định hiện đại của PHP và Laravel.

# Ứng dụng Laravel taste của một lập trình viên web PHP thực dụng
- Bắt đầu trên phiên bản Laravel và PHP mới nhất với khai báo kiểu nghiêm ngặt (strict types) trong mọi file. Độ tin cậy: 0.85
- Giữ controller mỏng và theo chuẩn resourceful, sử dụng single-action invokable controller cho bất kỳ tác vụ nào không phải CRUD. Độ tin cậy: 0.85
- Xác thực dữ liệu đầu vào trong các lớp Form Request và phân quyền bằng Policy thay vì kiểm tra trực tiếp trong controller. Độ tin cậy: 0.90
- Đặt logic nghiệp vụ trong các lớp action nhỏ như CreateInvoice, không đặt trong controller, model hay route closure. Độ tin cậy: 0.75
- Sử dụng Eloquent với các trường $fillable hoặc casts tường minh, và không bao giờ gọi Model::unguard trong code production. Độ tin cậy: 0.85
- Bật Model::preventLazyLoading ngoài môi trường production để phát hiện sớm truy vấn N+1 và khắc phục bằng eager loading. Độ tin cậy: 0.90
- Đảm bảo mọi quan hệ đều có khóa ngoại (foreign key) và chỉ mục duy nhất (unique index) trong migration, chứ không chỉ dựa vào quy tắc của Eloquent. Độ tin cậy: 0.90
- Viết các migration an toàn khi chạy tiến (roll forward) và không bao giờ sửa đổi một migration đã chạy trên production. Độ tin cậy: 0.90
- Định hình phản hồi API bằng API Resources để cấu trúc cơ sở dữ liệu không bao giờ bị lộ trực tiếp ra JSON. Độ tin cậy: 0.85
- Đẩy các tác vụ chậm như gửi email, xuất dữ liệu và gọi dịch vụ bên thứ ba vào các hàng đợi (queued jobs) chạy dưới Horizon, đồng thời đảm bảo mọi job đều có tính idempotent. Độ tin cậy: 0.90
- Chỉ sử dụng event và listener cho các tác dụng phụ (side effects) thực sự, không dùng để che giấu luồng xử lý chính của một request. Độ tin cậy: 0.75
- Đọc cấu hình thông qua config() và chỉ giữ các lệnh gọi env() bên trong các file config để tính năng lưu bộ nhớ đệm cấu hình (config caching) hoạt động. Độ tin cậy: 0.95
- Chạy php artisan optimize, config:cache và route:cache như một phần của mọi quy trình triển khai (deploy). Độ tin cậy: 0.85
- Sử dụng Sanctum cho xác thực SPA chính chủ (first-party) và ứng dụng di động, và chỉ dùng đến Passport khi bạn cần OAuth đầy đủ. Độ tin cậy: 0.80
- Kiểm thử bằng Pest hoặc PHPUnit sử dụng feature test trên cơ sở dữ liệu thực kết hợp với RefreshDatabase. Độ tin cậy: 0.85
- Tạo dữ liệu kiểm thử bằng model factory và state thay vì các file seed dùng chung kích thước lớn. Độ tin cậy: 0.80
- Áp đặt chuẩn định dạng code bằng Laravel Pint và phân tích tĩnh bằng Larastan ở mức 6 trở lên trong CI. Độ tin cậy: 0.85
- Ưu tiên sử dụng Livewire hoặc Inertia cùng các bộ starter kit của Laravel trước khi xây dựng một SPA tách biệt. Độ tin cậy: 0.75
- Giới hạn tần suất (rate limit) cho các route đăng nhập và API công khai bằng các bộ giới hạn tần suất được đặt tên (named rate limiters). Độ tin cậy: 0.90
- Lưu trữ file tải lên thông qua Storage facade trên các ổ đĩa tương thích S3 và phục vụ các file riêng tư qua URL có chữ ký tạm thời (temporary signed URLs). Độ tin cậy: 0.85
- Lên lịch các tác vụ định kỳ trong bộ lập lịch (scheduler) với withoutOverlapping và onOneServer, thay vì viết trực tiếp trong crontab thô. Độ tin cậy: 0.85
- Giữ số lượng gói thư viện (packages) ít và được bảo trì tốt, đồng thời nâng cấp từng phiên bản chính (major version) của Laravel một theo hướng dẫn nâng cấp chính thức. Độ tin cậy: 0.80
PHPBackendWeb Development+1
F@f
0
Trước4 / 4