feat: implement 8 features across full stack

- Leaderboard: rank-based percentile tiers (champion/gold/silver/bronze),
    deterministic tie-breaking (xp DESC, user_id ASC)
  - Mentor chat: md: breakpoints, safe-area-inset-bottom padding
  - Learning progress: server-side progressPct, curriculum expansion safety
  - Physical orders: hard-disabled with 410 Gone
  - Certificate approval: pending/issued flow, admin issue/revoke endpoints,
    certificate_requires_approval setting (migration 0031)
  - Draft/publish: is_published on lessons and chapters, admin publish UI
    (migration 0030)
  - Video panel: static thumbnails (no preload), client-side pagination
  - Rich editor: TipTap + tiptap-markdown for lesson descriptions,
    marked + DOMPurify for SafeMarkdown rendering
This commit is contained in:
ahp
2026-10-18 10:14:22 +03:30
parent 75e2892e36
commit 03610bc3d3
23 changed files with 1641 additions and 474 deletions
+32
View File
@@ -575,6 +575,38 @@ func (s *Server) handleAdminDeleteLesson(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
func (s *Server) handleAdminPublishLesson(w http.ResponseWriter, r *http.Request) {
id := routeID(r, "id")
var body struct {
Publish bool `json:"publish"`
}
if err := bodyJSON(r, &body); err != nil {
writeErr(w, http.StatusBadRequest, "دادهٔ ارسالی نامعتبر است")
return
}
if err := s.store.PublishLesson(r.Context(), id, body.Publish); err != nil {
writeErr(w, http.StatusInternalServerError, "به‌روزرسانی وضعیت درس ممکن نشد")
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
func (s *Server) handleAdminPublishChapter(w http.ResponseWriter, r *http.Request) {
id := routeID(r, "id")
var body struct {
Publish bool `json:"publish"`
}
if err := bodyJSON(r, &body); err != nil {
writeErr(w, http.StatusBadRequest, "دادهٔ ارسالی نامعتبر است")
return
}
if err := s.store.PublishChapter(r.Context(), id, body.Publish); err != nil {
writeErr(w, http.StatusInternalServerError, "به‌روزرسانی وضعیت فصل ممکن نشد")
return
}
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
// ---- questions ----
func (s *Server) handleAdminListQuestions(w http.ResponseWriter, r *http.Request) {