package routes import ( "backend/database" "database/sql" "encoding/json" "errors" "fmt" "github.com/go-chi/chi/v5" "io" "net/http" "os" "path/filepath" "strconv" "strings" ) func RegisterNovelsRoutes(r chi.Router) { r.Route("/", func(s chi.Router) { s.Get("/", getNovels) s.Post("/", createNovel) }) r.Route("/{novelId:[0-9]+}", func(s chi.Router) { s.Get("/", getNovel) s.Put("/", updateNovel) s.Delete("/", deleteNovel) }) r.Post("/{novelId:[0-9]+}/cover", uploadNovelCover) r.Route("/{novelId:[0-9]+}/comments", func(s chi.Router) { s.Get("/", getNovelComments) s.Post("/", createNovelComment) }) r.Get("/{novelId:[0-9]+}/characters", resolveNovelCharacters) r.Get("/{novelId:[0-9]+}/genres", resolveNovelGenres) } func getNovels(w http.ResponseWriter, r *http.Request) { sortBy, ok := r.URL.Query()["sort"] if !ok || len(sortBy) <= 0 { sortBy = append(sortBy, "rating") } query, ok := r.URL.Query()["q"] if ok { novels, err := database.SearchNovels(query[0]) Response(w, err, novels) return } novels, err := database.FetchNovels(sortBy[0]) Response(w, err, &novels) } func createNovel(w http.ResponseWriter, r *http.Request) { body, err := ReadBody(r) if err != nil { Error(w, err) return } var novel database.Novel err = json.Unmarshal(body, &novel) if err != nil { Error(w, err) return } novel, err = database.CreateNovel(&novel) if err == nil { w.WriteHeader(http.StatusCreated) w.Header().Add("Location", fmt.Sprintf("https://%v%v/%v", r.Host, r.URL, novel.ID)) } Response(w, err, &novel) } func getNovel(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") novel, err := database.FetchNovel(novelId) Response(w, err, novel) } func updateNovel(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") body, err := ReadBody(r) if err != nil { Error(w, err) return } novel, err := database.FetchNovel(novelId) if err != nil { Response(w, err, nil) return } err = json.Unmarshal(body, novel) if err != nil { Error(w, err) return } novel, err = database.UpdateNovel(novel) Response(w, err, novel) } func deleteNovel(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") _, err := database.FetchNovel(novelId) if errors.Is(err, sql.ErrNoRows) { NotFound.Ferror(w) return } err = database.DeleteNovel(novelId) if err != nil { w.WriteHeader(500) Error(w, err) return } w.WriteHeader(http.StatusNoContent) JSON(w, "") } func uploadNovelCover(w http.ResponseWriter, r *http.Request) { user, err := GetCurrentUser(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) Error(w, err) return } if !hasPermission(user, "novel") { NoAccess.Ferror(w) return } novelId := IURLParam(r, "novelId") err = r.ParseMultipartForm(1024 * 1024 * 2) if err != nil { w.WriteHeader(http.StatusBadRequest) Error(w, err) return } file, handler, err := r.FormFile("cover") if err != nil { w.WriteHeader(http.StatusBadRequest) Error(w, err) return } if !strings.HasPrefix(handler.Header["Content-Type"][0], "image") { w.WriteHeader(http.StatusBadRequest) RawError(w, "Wrong document type!") return } fileBytes, err := io.ReadAll(file) if err != nil { w.WriteHeader(http.StatusInternalServerError) Error(w, err) return } err = file.Close() if err != nil { w.WriteHeader(http.StatusInternalServerError) Error(w, err) return } ext := strings.Split(handler.Header["Content-Type"][0], "/")[1] path := filepath.Join(os.Getenv("CDN_PATH"), "novels", "logo", fmt.Sprintf("%v.%v", novelId, ext)) err = os.WriteFile(path, fileBytes, 0644) if err != nil { w.WriteHeader(http.StatusInternalServerError) Error(w, err) return } coverPath := fmt.Sprintf("%v/novels/logo/%v.%v", os.Getenv("CDN_URL"), novelId, ext) err = database.UpdateNovelCover(novelId, coverPath) Response(w, err, coverPath) } func getNovelComments(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") offsetParam, ok := r.URL.Query()["offset"] if !ok || len(offsetParam) <= 0 { offsetParam = append(offsetParam, "0") } countParam, ok := r.URL.Query()["count"] if !ok || len(countParam) <= 0 { countParam = append(countParam, "5") } offset, _ := strconv.Atoi(offsetParam[0]) count, _ := strconv.Atoi(countParam[0]) var response = make(map[string]interface{}, 2) comments, err := database.FetchNovelComments(novelId) response["count"] = len(comments) if offset+count > len(comments) { comments = comments[offset:] } else { comments = comments[offset : offset+count] } response["comments"] = comments Response(w, err, response) } func createNovelComment(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") user, err := GetCurrentUser(r) if err != nil { Error(w, err) return } body, err := ReadBody(r) if err != nil { Error(w, err) return } commentData := database.CommentNovel{NovelID: int16(novelId), UserID: user.ID} err = json.Unmarshal(body, &commentData) if err != nil { Error(w, err) return } comment, err := database.AddNovelComment(commentData) Response(w, err, comment) } func resolveNovelGenres(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") genres, err := database.FetchNovelGenres(novelId) Response(w, err, genres) } func resolveNovelCharacters(w http.ResponseWriter, r *http.Request) { novelId := IURLParam(r, "novelId") characters, err := database.FetchNovelCharacters(novelId) Response(w, err, characters) }