feat(courses): CRUD курсов
All checks were successful
CI / test (push) Successful in 14s
Build and Deploy / build-and-deploy (push) Successful in 25s

Базовая работа с курсами (без уроков — добавятся в следующей итерации).

CourseRepository:
- List с тем же паттерном что TestRepository: ownerFilter +
  onlyPublished + visibleIDs (для будущего access_grants).
- Get / GetBySlug — slug нужен для public-страниц.
- Create — slugify(title) если slug не задан; collision retry до 5 раз
  (UNIQUE constraint courses_slug_key).
- Update / Delete с CASCADE на lessons.
- courseCols + lessons_count subquery, UI получает бейдж без отдельного
  запроса.

CourseHandler — стандартный набор. Гейтит owner для write/delete;
read доступен всем (внутри сервиса), portal проксирует под
service.learning.access.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilya
2026-05-25 23:31:20 +03:00
parent 47a76bef7c
commit 350703ab83
3 changed files with 357 additions and 3 deletions

View File

@@ -45,10 +45,12 @@ func main() {
testRepo := repository.NewTestRepository(pool)
attemptRepo := repository.NewAttemptRepository(pool)
courseRepo := repository.NewCourseRepository(pool)
healthH := handler.NewHealthHandler(pool)
testH := handler.NewTestHandler(testRepo)
attemptH := handler.NewAttemptHandler(attemptRepo, testRepo)
courseH := handler.NewCourseHandler(courseRepo)
r := chi.NewRouter()
r.Use(chimw.RequestID)
@@ -84,9 +86,12 @@ func main() {
r.Get("/attempts/{id}", attemptH.Get)
r.Post("/attempts/{id}/submit", attemptH.Submit)
// Заглушки на следующие итерации.
r.HandleFunc("/courses", notImplemented)
r.HandleFunc("/courses/{id}", notImplemented)
// Courses CRUD. Lessons/video — следующая итерация.
r.Get("/courses", courseH.List)
r.Post("/courses", courseH.Create)
r.Get("/courses/{id}", courseH.Get)
r.Patch("/courses/{id}", courseH.Update)
r.Delete("/courses/{id}", courseH.Delete)
r.HandleFunc("/courses/{id}/lessons", notImplemented)
r.HandleFunc("/lessons/{id}", notImplemented)
r.HandleFunc("/lessons/{id}/video", notImplemented)