Files
ad_fun/chessstars/internal/courses/handlers.go
T
2026-07-15 13:29:08 +03:00

218 lines
6.1 KiB
Go

package courses
import (
"net/http"
"strconv"
"chessstars/internal/auth"
"chessstars/internal/httpx"
)
type Handlers struct {
store *Store
}
func NewHandlers(store *Store) *Handlers {
return &Handlers{store: store}
}
func pathID(r *http.Request, name string) (int64, bool) {
v := r.PathValue(name)
id, err := strconv.ParseInt(v, 10, 64)
return id, err == nil
}
func canView(c *Course, u *auth.User) bool {
if c.Visibility == "public" {
return true
}
return u != nil && u.ID == c.AuthorID
}
type chapterSummary struct {
ID int64 `json:"id"`
Ordinal int `json:"ordinal"`
Title string `json:"title"`
}
type courseDetail struct {
ID int64 `json:"id"`
Author string `json:"author"`
Title string `json:"title"`
Description string `json:"description"`
Visibility string `json:"visibility"`
Variant int `json:"variant"`
StartFEN string `json:"start_fen"`
RuleFlags uint32 `json:"rule_flags"`
Chapters []chapterSummary `json:"chapters"`
}
func (h *Handlers) CreateCourse(w http.ResponseWriter, r *http.Request) {
u, ok := auth.UserFromContext(r.Context())
if !ok {
httpx.Error(w, http.StatusUnauthorized, "not logged in")
return
}
var req struct {
Title string `json:"title"`
Description string `json:"description"`
Visibility string `json:"visibility"`
Variant int `json:"variant"`
StartFEN string `json:"start_fen"`
RuleFlags uint32 `json:"rule_flags"`
}
if err := httpx.ReadJSON(r, &req); err != nil || req.Title == "" {
httpx.Error(w, http.StatusBadRequest, "invalid request")
return
}
c, err := h.store.CreateCourse(Course{
AuthorID: u.ID, AuthorName: u.Username, Title: req.Title, Description: req.Description,
Visibility: req.Visibility, Variant: req.Variant, StartFEN: req.StartFEN, RuleFlags: req.RuleFlags,
})
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "could not create course")
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "id": c.ID})
}
func (h *Handlers) ListPublicCourses(w http.ResponseWriter, r *http.Request) {
list, err := h.store.ListPublicCourses()
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "list failed")
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "courses": list})
}
func (h *Handlers) ListMyCourses(w http.ResponseWriter, r *http.Request) {
u, ok := auth.UserFromContext(r.Context())
if !ok {
httpx.Error(w, http.StatusUnauthorized, "not logged in")
return
}
list, err := h.store.ListCoursesByAuthor(u.ID)
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "list failed")
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "courses": list})
}
func (h *Handlers) GetCourse(w http.ResponseWriter, r *http.Request) {
id, ok := pathID(r, "id")
if !ok {
httpx.Error(w, http.StatusBadRequest, "bad id")
return
}
c, err := h.store.GetCourse(id)
if err != nil {
httpx.Error(w, http.StatusNotFound, "no such course")
return
}
u, _ := auth.UserFromContext(r.Context())
if !canView(c, u) {
httpx.Error(w, http.StatusNotFound, "no such course")
return
}
chapters, err := h.store.ListChaptersByCourse(c.ID)
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "list failed")
return
}
summaries := make([]chapterSummary, 0, len(chapters))
for _, ch := range chapters {
summaries = append(summaries, chapterSummary{ID: ch.ID, Ordinal: ch.Ordinal, Title: ch.Title})
}
httpx.WriteJSON(w, http.StatusOK, courseDetail{
ID: c.ID, Author: c.AuthorName, Title: c.Title, Description: c.Description,
Visibility: c.Visibility, Variant: c.Variant, StartFEN: c.StartFEN, RuleFlags: c.RuleFlags,
Chapters: summaries,
})
}
func (h *Handlers) AddChapter(w http.ResponseWriter, r *http.Request) {
u, ok := auth.UserFromContext(r.Context())
if !ok {
httpx.Error(w, http.StatusUnauthorized, "not logged in")
return
}
courseID, ok := pathID(r, "id")
if !ok {
httpx.Error(w, http.StatusBadRequest, "bad id")
return
}
c, err := h.store.GetCourse(courseID)
if err != nil {
httpx.Error(w, http.StatusNotFound, "no such course")
return
}
if c.AuthorID != u.ID {
httpx.Error(w, http.StatusForbidden, "not your course")
return
}
var req struct {
Ordinal int `json:"ordinal"`
Title string `json:"title"`
Annotation string `json:"annotation"`
Moves []MoveRef `json:"moves"`
SolutionNote string `json:"solution_note"`
}
if err := httpx.ReadJSON(r, &req); err != nil || req.Title == "" {
httpx.Error(w, http.StatusBadRequest, "invalid request")
return
}
ch, err := h.store.AddChapter(Chapter{
CourseID: c.ID, Ordinal: req.Ordinal, Title: req.Title, Annotation: req.Annotation,
Moves: req.Moves, SolutionNote: req.SolutionNote,
})
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "could not add chapter")
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "id": ch.ID})
}
type chapterDetail struct {
ID int64 `json:"id"`
CourseID int64 `json:"course_id"`
Ordinal int `json:"ordinal"`
Title string `json:"title"`
Annotation string `json:"annotation"`
Moves []MoveRef `json:"moves"`
SolutionNote string `json:"solution_note"`
}
func (h *Handlers) GetChapter(w http.ResponseWriter, r *http.Request) {
if _, ok := auth.UserFromContext(r.Context()); !ok {
httpx.Error(w, http.StatusUnauthorized, "not logged in")
return
}
courseID, ok := pathID(r, "id")
if !ok {
httpx.Error(w, http.StatusBadRequest, "bad course id")
return
}
chapterID, ok := pathID(r, "chapterID")
if !ok {
httpx.Error(w, http.StatusBadRequest, "bad chapter id")
return
}
ch, err := h.store.GetChapter(courseID, chapterID)
if err != nil {
httpx.Error(w, http.StatusNotFound, "no such chapter")
return
}
httpx.WriteJSON(w, http.StatusOK, chapterDetail{
ID: ch.ID, CourseID: ch.CourseID, Ordinal: ch.Ordinal, Title: ch.Title,
Annotation: ch.Annotation, Moves: ch.Moves, SolutionNote: ch.SolutionNote,
})
}