218 lines
6.1 KiB
Go
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,
|
|
})
|
|
}
|