SDD-036 — Con tự kể về mình: lời tự thuật và ảnh thật của learner
Chỉ đạo chủ dự án 2026-09-15: "Learner có thể khai báo thêm sở thích, quá khứ, điểm mạnh, điểm yếu... để các Teacher có thể hiểu thêm được. Learner cần upload ảnh thật của chính mình lên."
1. Nguyên tắc
- Người đọc là NGƯỜI, không phải engine. Mục đích chủ dự án nêu là "để các Teacher có thể hiểu thêm được". Vì thế các ô là văn xuôi, không phải danh sách chọn. Đã cân nhắc một bảng sở thích có mã (
football,manga…) và bỏ: danh sách chọn ép mọi đứa trẻ vào những ô ta nghĩ sẵn, và bỏ mất đúng câu đáng đọc nhất — "con từng bỏ học tiếng Anh một năm vì cô giáo cũ chê con phát âm". Ngày nào có engine thật sự cần đọc máy được thì rút trích từ văn xuôi, chứ không bắt đứa trẻ khai hai lần. - Không cắt chữ khi hiển thị cho mentor. Không "xem thêm", không tóm tắt. Ba câu một đứa trẻ viết về chính nó thì mentor cần đúng ba câu ấy.
- Ô trống là một câu trả lời hợp lệ, và giao diện nói thẳng điều đó. Một biểu mẫu năm ô bắt điền đủ sẽ được điền bừa cho xong, mà điền bừa còn tệ hơn để trống.
- Ảnh trẻ em không bao giờ là media công khai (Q-146). Nó không có "người đọc chung" nào.
2. Dữ liệu (migration 0225_learner_self_profile.sql)
learner_self_profiles: một dòng mỗi learner, năm ô văn xuôi — interests · background · strengths · weaknesses · note, mỗi ô trần 2000 ký tự.
Vì sao bảng riêng, không thêm cột vào learners: learners giữ thứ hệ thống cần để vận hành (lớp, trường, trạng thái) và bị mọi engine SELECT liên tục. Bảng này giữ mấy nghìn ký tự tâm sự mà không engine nào dùng tới.
Ô cuối note ("điều con muốn thầy cô biết") tồn tại vì bốn ô trên là câu hỏi của người lớn; ô này là chỗ cho thứ đứa trẻ muốn nói mà ta không nghĩ ra để hỏi.
Ảnh: KHÔNG thêm cột nào. learners.photo_media_id đã có từ migrations/0083_family_member_photos_nickname.sql (SRC-607) cho ảnh mentor tải lên trong Family Workspace. Thêm cột thứ hai cho "ảnh do chính con tải lên" là tạo hai câu trả lời cho cùng câu hỏi "ảnh của đứa trẻ này là tấm nào", và đến ngày hai cột lệch nhau thì không ai biết tấm nào đúng.
3. Ảnh: vì sao có một lối phục vụ riêng
GET /v1/media/{id} phục vụ ảnh nội bộ cho VAI nội bộ: mentor, staff, admin. Đó đúng là điều Family Workspace cần, nhưng nó bỏ sót hai người quan trọng nhất ở đây — chính đứa trẻ và bố mẹ nó, những người không có vai nào cả. Một tính năng "tải ảnh của con lên" mà con không xem lại được ảnh của mình là một tính năng hỏng.
Nên ảnh đi thêm lối GET /v1/learners/{id}/photo, gác bằng quan hệ (requireLearnerAccess: chính em ấy · người nhà · mentor/staff/admin). Không hạ điều kiện của lối cũ: nới /v1/media/{id} để hai người này lọt qua là nới cho mọi ảnh nội bộ của cả hệ chỉ để phục vụ một màn hình.
Chi tiết đã chốt:
visibility='internal', vàmoderation_status='approved'— không phảipending. Ảnh dùng chung cột với ảnh gia đình vốn đãapproved; đểpendinglà làm vỡ ảnh ngay trên Family Workspace đang chạy, và không có hàng đợi duyệt nào để thoát ra. Ngày nào ảnh learner được phép ra khỏi vòng gia đình + thầy cô thì phải bật lạipendingvà dựng hàng đợi duyệt trong cùng một vòng (Q-132).Cache-Control: private, no-store, không ETag: ảnh một đứa trẻ thì CDN và trình duyệt trung gian không được giữ bản sao, và một ETag ổn định là một mã định danh dùng lại được.- Tên tệp client gửi bị bỏ hẳn, đuôi suy từ
Content-Type— cùng luật vớishowcase/media. - Đổi ảnh = trỏ sang
media.idmới; chuỗi byte cũ không bị xoá (SDD-019 §1). Gỡ ảnh = bỏ trỏ, giữ bản ghi để truy vết. GET self-profiletrảhas_photo(boolean) chứ không trảmedia_id: id không mở được gì qua lối này, nên đưa ra chỉ là rò một mã định danh mà người nhận không có việc gì để dùng.
4. API
| Route | Việc |
|---|---|
GET·PUT /v1/learners/{id}/self-profile | Đọc / ghi năm ô, kèm has_photo |
POST·GET·DELETE /v1/learners/{id}/photo | Tải lên (bytes thuần, ≤ 5 MB, JPG/PNG/WebP) · xem · gỡ |
Ghi là gộp từng phần: ô không gửi lên thì giữ nguyên, ô gửi chuỗi rỗng thì xoá. Ghi đè cả bản ghi bằng những ô client tình cờ không gửi là cách xoá lời một đứa trẻ đã viết mà không ai biết.
Mọi lối đi qua requireLearnerAccess (QG-008), nên mỗi lượt mentor mở hồ sơ một đứa trẻ để lại một dòng audit_log.
5. Giao diện
apps/learn/src/SelfProfileCard.tsx, cụm cuối của trang Hồ sơ. Mỗi ô nói rõ ai sẽ đọc ngay trên nhãn: một đứa trẻ không biết ai đọc thì hoặc không viết, hoặc viết cho có.apps/mentors/src/LearnerAbout.tsx, tab About đứng ngay sau Summary ở màn quan sát learner. Trước buổi dạy đầu tiên, biết đứa trẻ này là ai quan trọng hơn biết nó vững node nào — và đây là tab duy nhất trong cổng mà learner nói trước.
6. Phần CỐ Ý chưa làm
- Duyệt ảnh. Xem §3: chỉ cần khi ảnh learner ra khỏi vòng gia đình + thầy cô.
- Cắt/xoay ảnh trong trình duyệt. Ảnh chụp dọc bằng điện thoại có thể hiện nghiêng. Chờ xem có thật sự xảy ra không rồi hẵng kéo thư viện xử lý ảnh về (DS-001 §0 cấm thêm gói khi chưa cần).
- Rút trích có cấu trúc từ văn xuôi cho engine đọc — xem §1 nguyên tắc 1.
Trace
| REQ | Section |
|---|---|
| REQ-LRN-46 | §1, §2, §3, §4, §5 |