feat(lessons): ListVideos — плоский endpoint для раздела «Видео-уроки»
All checks were successful
CI / test (push) Successful in 19s
Build and Deploy / build-and-deploy (push) Successful in 26s

LessonRepository.ListVideos: SELECT с INNER JOIN courses, фильтр
video_key != '' + (course.is_published OR course.owner_user_id = viewer).
Возвращает LessonWithCourse — урок + denorm course_{title,slug,
is_published,owner_user_id} чтобы фронт сгруппировал по курсу
без N+1.

LessonHandler.ListVideos: GET /lessons?has_video=true. Гейт уже на
SQL-уровне, в коде только X-User-Id из headers.

Route регистрируется ДО /lessons/{id}, иначе chi бы заматчил
{id}="lessons".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilya
2026-05-26 00:17:34 +03:00
parent 80c019b791
commit 400df0124d
3 changed files with 70 additions and 0 deletions

View File

@@ -63,6 +63,23 @@ func (h *LessonHandler) ListByCourse(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// ListVideos — GET /lessons?has_video=true. Плоский список уроков с
// видео для отдельной страницы «Видео-уроки» в портале. Гейтит на уровне
// репо: только опубликованные курсы или мои.
func (h *LessonHandler) ListVideos(w http.ResponseWriter, r *http.Request) {
uid, ok := userIDFromHeader(r)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
items, err := h.repo.ListVideos(r.Context(), uid)
if err != nil {
writeRepoError(w, r, err, "list video lessons")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (h *LessonHandler) Get(w http.ResponseWriter, r *http.Request) {
id, err := parseUUID(chi.URLParam(r, "id"))
if err != nil {