657 lines
17 KiB
Go
657 lines
17 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"mal/internal/db"
|
|
"mal/internal/domain"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type AnimeHandler struct {
|
|
svc domain.AnimeService
|
|
watchlistSvc domain.WatchlistService
|
|
}
|
|
|
|
func NewAnimeHandler(svc domain.AnimeService, watchlistSvc domain.WatchlistService) *AnimeHandler {
|
|
return &AnimeHandler{
|
|
svc: svc,
|
|
watchlistSvc: watchlistSvc,
|
|
}
|
|
}
|
|
|
|
func (h *AnimeHandler) watchlistMapForAnimes(ctx context.Context, userID string, animes []domain.Anime) map[int64]bool {
|
|
animeIDs := make([]int64, 0, len(animes))
|
|
for _, anime := range animes {
|
|
if anime.MalID > 0 {
|
|
animeIDs = append(animeIDs, int64(anime.MalID))
|
|
}
|
|
}
|
|
return h.watchlistMapForIDs(ctx, userID, animeIDs)
|
|
}
|
|
|
|
func (h *AnimeHandler) watchlistMapForIDs(ctx context.Context, userID string, animeIDs []int64) map[int64]bool {
|
|
if userID == "" || len(animeIDs) == 0 {
|
|
return map[int64]bool{}
|
|
}
|
|
|
|
watchlistMap, err := h.watchlistSvc.GetWatchlistMap(ctx, userID, animeIDs)
|
|
if err != nil {
|
|
return map[int64]bool{}
|
|
}
|
|
return watchlistMap
|
|
}
|
|
|
|
func (h *AnimeHandler) Register(r *gin.Engine) {
|
|
|
|
r.GET("/", h.HandleCatalog)
|
|
r.GET("/api/catalog/airing", h.HandleCatalogAiring)
|
|
r.GET("/api/catalog/popular", h.HandleCatalogPopular)
|
|
r.GET("/api/catalog/continue", h.HandleCatalogContinue)
|
|
r.GET("/discover", h.HandleDiscover)
|
|
r.GET("/api/discover/trending", h.HandleDiscoverTrending)
|
|
r.GET("/api/discover/upcoming", h.HandleDiscoverUpcoming)
|
|
r.GET("/api/discover/top", h.HandleDiscoverTop)
|
|
r.GET("/browse", h.HandleBrowse)
|
|
r.GET("/anime/:id", h.HandleAnimeDetails)
|
|
r.GET("/anime/:id/reviews", h.HandleAnimeReviews)
|
|
r.GET("/api/watch-order", h.HandleHTMLWatchOrder)
|
|
r.GET("/api/search-quick", h.HandleQuickSearch)
|
|
r.GET("/api/command-palette", h.HandleCommandPalette)
|
|
r.GET("/api/jikan/random/anime", h.HandleRandomAnime)
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleCatalog(c *gin.Context) {
|
|
user, _ := c.Get("User")
|
|
|
|
c.HTML(http.StatusOK, "index.gohtml", gin.H{
|
|
"CurrentPath": "/",
|
|
"User": user,
|
|
"WatchlistMap": map[int64]bool{},
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleCatalogAiring(c *gin.Context) {
|
|
h.renderCatalogSection(c, "Airing")
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleCatalogPopular(c *gin.Context) {
|
|
h.renderCatalogSection(c, "Popular")
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleCatalogContinue(c *gin.Context) {
|
|
h.renderCatalogSection(c, "Continue")
|
|
}
|
|
|
|
func (h *AnimeHandler) renderCatalogSection(c *gin.Context, section string) {
|
|
user, _ := c.Get("User")
|
|
userID := ""
|
|
if u, ok := user.(*domain.User); ok {
|
|
userID = u.ID
|
|
}
|
|
data, err := h.svc.GetCatalogSection(c.Request.Context(), userID, section)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, data.Animes)
|
|
|
|
data.Section = section
|
|
data.Fragment = "catalog_section"
|
|
data.WatchlistMap = watchlistMap
|
|
c.HTML(http.StatusOK, "index.gohtml", data)
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleDiscover(c *gin.Context) {
|
|
user, _ := c.Get("User")
|
|
c.HTML(http.StatusOK, "discover.gohtml", gin.H{
|
|
"CurrentPath": "/discover",
|
|
"User": user,
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleDiscoverTrending(c *gin.Context) {
|
|
h.renderDiscoverSection(c, "Trending")
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleDiscoverUpcoming(c *gin.Context) {
|
|
h.renderDiscoverSection(c, "Upcoming")
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleDiscoverTop(c *gin.Context) {
|
|
h.renderDiscoverSection(c, "Top")
|
|
}
|
|
|
|
func (h *AnimeHandler) renderDiscoverSection(c *gin.Context, section string) {
|
|
user, _ := c.Get("User")
|
|
userID := ""
|
|
if u, ok := user.(*domain.User); ok {
|
|
userID = u.ID
|
|
}
|
|
data, err := h.svc.GetDiscoverSection(c.Request.Context(), userID, section)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, data.Animes)
|
|
|
|
data.Section = section
|
|
data.Fragment = "discover_section"
|
|
data.WatchlistMap = watchlistMap
|
|
c.HTML(http.StatusOK, "discover.gohtml", data)
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleBrowse(c *gin.Context) {
|
|
q := c.Query("q")
|
|
animeType := c.Query("type")
|
|
status := c.Query("status")
|
|
orderBy := c.Query("order_by")
|
|
sort := c.Query("sort")
|
|
sfw := c.Query("sfw") != "false"
|
|
|
|
var genres []int
|
|
for _, g := range c.QueryArray("genres") {
|
|
id, _ := strconv.Atoi(g)
|
|
if id > 0 {
|
|
genres = append(genres, id)
|
|
}
|
|
}
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
|
|
res, err := h.svc.SearchAdvanced(c.Request.Context(), q, animeType, status, orderBy, sort, genres, sfw, page, 24)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
userID := ""
|
|
if u, ok := user.(*domain.User); ok {
|
|
userID = u.ID
|
|
}
|
|
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, res.Animes)
|
|
|
|
if c.GetHeader("HX-Request") == "true" && page > 1 {
|
|
c.HTML(http.StatusOK, "browse.gohtml", gin.H{
|
|
"_fragment": "anime_card_scroll",
|
|
"Animes": res.Animes,
|
|
"NextPage": page + 1,
|
|
"HasNextPage": res.HasNextPage,
|
|
"Query": q,
|
|
"Type": animeType,
|
|
"Status": status,
|
|
"OrderBy": orderBy,
|
|
"Sort": sort,
|
|
"Genres": genres,
|
|
"SFW": sfw,
|
|
"WatchlistMap": watchlistMap,
|
|
})
|
|
return
|
|
}
|
|
|
|
genresList, _ := h.svc.GetGenres(c.Request.Context())
|
|
|
|
if c.GetHeader("HX-Request") == "true" {
|
|
c.HTML(http.StatusOK, "browse.gohtml", gin.H{
|
|
"_fragment": "browse_content",
|
|
"CurrentPath": "/browse",
|
|
"Query": q,
|
|
"Type": animeType,
|
|
"Status": status,
|
|
"OrderBy": orderBy,
|
|
"Sort": sort,
|
|
"Genres": genres,
|
|
"SFW": sfw,
|
|
"GenresList": genresList,
|
|
"Animes": res.Animes,
|
|
"HasNextPage": res.HasNextPage,
|
|
"NextPage": page + 1,
|
|
"User": user,
|
|
"WatchlistMap": watchlistMap,
|
|
})
|
|
return
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "browse.gohtml", gin.H{
|
|
"CurrentPath": "/browse",
|
|
"Query": q,
|
|
"Type": animeType,
|
|
"Status": status,
|
|
"OrderBy": orderBy,
|
|
"Sort": sort,
|
|
"Genres": genres,
|
|
"SFW": sfw,
|
|
"GenresList": genresList,
|
|
"Animes": res.Animes,
|
|
"HasNextPage": res.HasNextPage,
|
|
"NextPage": page + 1,
|
|
"User": user,
|
|
"WatchlistMap": watchlistMap,
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleAnimeDetails(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Param("id"))
|
|
if id <= 0 {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
section := c.Query("section")
|
|
if section != "" && c.GetHeader("HX-Request") == "true" {
|
|
sectionCtx, cancel := context.WithTimeout(c.Request.Context(), 4*time.Second)
|
|
defer cancel()
|
|
|
|
var data any
|
|
var tplName string
|
|
var err error
|
|
switch section {
|
|
case "characters":
|
|
data, err = h.svc.GetCharacters(sectionCtx, id)
|
|
tplName = "anime_characters"
|
|
case "recommendations":
|
|
data, err = h.svc.GetRecommendations(sectionCtx, id)
|
|
tplName = "anime_recommendations"
|
|
|
|
case "statistics":
|
|
data, err = h.svc.GetStatistics(sectionCtx, id)
|
|
tplName = "anime_statistics"
|
|
case "themes":
|
|
data, err = h.svc.GetThemes(sectionCtx, id)
|
|
tplName = "anime_themes"
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("failed to fetch section %s: %v", section, err)
|
|
c.Status(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "anime.gohtml", gin.H{
|
|
"_fragment": tplName,
|
|
"Items": data,
|
|
})
|
|
return
|
|
}
|
|
|
|
anime, err := h.svc.GetAnimeByID(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
status := ""
|
|
var watchlistIDs []int64
|
|
ep := 0
|
|
var cwSeconds float64
|
|
if u, ok := user.(*domain.User); ok {
|
|
entry, err := h.watchlistSvc.GetWatchListEntry(c.Request.Context(), u.ID, int64(id))
|
|
if err == nil {
|
|
status = entry.Status
|
|
watchlistIDs = []int64{entry.AnimeID}
|
|
}
|
|
|
|
cwEntry, err := h.watchlistSvc.GetContinueWatchingEntry(c.Request.Context(), u.ID, int64(id))
|
|
if err == nil && cwEntry.CurrentEpisode.Valid {
|
|
ep = int(cwEntry.CurrentEpisode.Int64)
|
|
cwSeconds = cwEntry.CurrentTimeSeconds
|
|
}
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "anime.gohtml", gin.H{
|
|
"Anime": anime,
|
|
"CurrentPath": fmt.Sprintf("/anime/%d", id),
|
|
"User": user,
|
|
"Status": status,
|
|
"WatchlistIDs": watchlistIDs,
|
|
"ContinueWatchingEp": ep,
|
|
"ContinueWatchingTime": cwSeconds,
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleHTMLWatchOrder(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Query("animeId"))
|
|
if id <= 0 {
|
|
c.Status(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
userID := ""
|
|
if u, ok := user.(*domain.User); ok {
|
|
userID = u.ID
|
|
}
|
|
|
|
relationsCtx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
relations, err := h.svc.GetRelations(relationsCtx, id)
|
|
if err != nil {
|
|
log.Printf("failed to fetch relations for anime %d: %v", id, err)
|
|
c.Status(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
relationAnimeIDs := make([]int64, 0, len(relations))
|
|
for _, relation := range relations {
|
|
if relation.Anime.MalID > 0 {
|
|
relationAnimeIDs = append(relationAnimeIDs, int64(relation.Anime.MalID))
|
|
}
|
|
}
|
|
watchlistMap := h.watchlistMapForIDs(c.Request.Context(), userID, relationAnimeIDs)
|
|
|
|
c.HTML(http.StatusOK, "anime.gohtml", gin.H{
|
|
"_fragment": "watch_order",
|
|
"Relations": relations,
|
|
"AnimeID": id,
|
|
"WatchlistMap": watchlistMap,
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleQuickSearch(c *gin.Context) {
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
c.JSON(http.StatusOK, []any{})
|
|
return
|
|
}
|
|
|
|
res, err := h.svc.SearchAdvanced(c.Request.Context(), query, "", "", "", "", nil, true, 1, 5)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, []any{})
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
userID := ""
|
|
if u, ok := user.(*domain.User); ok {
|
|
userID = u.ID
|
|
}
|
|
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, res.Animes)
|
|
|
|
type quickSearchResult struct {
|
|
ID int `json:"id"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
Year int `json:"year"`
|
|
Image string `json:"image"`
|
|
InWatchlist bool `json:"in_watchlist"`
|
|
}
|
|
|
|
output := make([]quickSearchResult, len(res.Animes))
|
|
for i, anime := range res.Animes {
|
|
output[i] = quickSearchResult{
|
|
ID: anime.MalID,
|
|
Title: anime.DisplayTitle(),
|
|
Type: anime.Type,
|
|
Year: anime.Year,
|
|
Image: anime.ImageURL(),
|
|
InWatchlist: watchlistMap[int64(anime.MalID)],
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, output)
|
|
}
|
|
|
|
type commandPaletteItem struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Label string `json:"label"`
|
|
Subtitle string `json:"subtitle"`
|
|
Href string `json:"href"`
|
|
Image string `json:"image,omitempty"`
|
|
Icon string `json:"icon,omitempty"`
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleCommandPalette(c *gin.Context) {
|
|
user, _ := c.Get("User")
|
|
u, ok := user.(*domain.User)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return
|
|
}
|
|
|
|
query := strings.TrimSpace(c.Query("q"))
|
|
items := make([]commandPaletteItem, 0, 12)
|
|
|
|
if query != "" {
|
|
items = append(items, commandPaletteItem{
|
|
ID: "search:" + strings.ToLower(query),
|
|
Type: "search",
|
|
Label: fmt.Sprintf("Search anime for %q", query),
|
|
Subtitle: "Browse",
|
|
Href: "/browse?q=" + url.QueryEscape(query),
|
|
Icon: "search",
|
|
})
|
|
|
|
if len(query) >= 2 {
|
|
items = append(items, h.commandPaletteAnimeResults(c, query)...)
|
|
}
|
|
|
|
items = append(items, h.commandPaletteNavigationItems(query)...)
|
|
items = append(items, h.commandPaletteContinueItems(c, u.ID, query)...)
|
|
items = append(items, h.commandPalettePersonalItems(c, u.ID, query)...)
|
|
c.JSON(http.StatusOK, items)
|
|
return
|
|
}
|
|
|
|
items = append(items, h.commandPaletteContinueItems(c, u.ID, query)...)
|
|
items = append(items, h.commandPaletteNavigationItems(query)...)
|
|
items = append(items, h.commandPalettePersonalItems(c, u.ID, query)...)
|
|
c.JSON(http.StatusOK, items)
|
|
}
|
|
|
|
func (h *AnimeHandler) commandPaletteNavigationItems(query string) []commandPaletteItem {
|
|
all := []commandPaletteItem{
|
|
{ID: "nav:discover", Type: "navigation", Label: "Go to Discover", Subtitle: "Navigation", Href: "/discover", Icon: "compass"},
|
|
{ID: "nav:watchlist", Type: "navigation", Label: "Go to Watchlist", Subtitle: "Navigation", Href: "/watchlist", Icon: "bookmark"},
|
|
{ID: "nav:popular", Type: "navigation", Label: "Browse popular", Subtitle: "Browse", Href: "/browse?order_by=popularity&sort=desc", Icon: "trending"},
|
|
{ID: "nav:airing", Type: "navigation", Label: "Currently airing", Subtitle: "Browse", Href: "/browse?status=airing&order_by=popularity&sort=desc", Icon: "play"},
|
|
}
|
|
if query == "" {
|
|
return all
|
|
}
|
|
|
|
filtered := make([]commandPaletteItem, 0, len(all))
|
|
for _, item := range all {
|
|
if commandPaletteMatches(query, item.Label, item.Subtitle) {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func (h *AnimeHandler) commandPaletteAnimeResults(c *gin.Context, query string) []commandPaletteItem {
|
|
searchCtx, cancel := context.WithTimeout(c.Request.Context(), 800*time.Millisecond)
|
|
defer cancel()
|
|
|
|
res, err := h.svc.SearchAdvanced(searchCtx, query, "", "", "", "", nil, true, 1, 5)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
items := make([]commandPaletteItem, 0, len(res.Animes))
|
|
for _, anime := range res.Animes {
|
|
items = append(items, commandPaletteItem{
|
|
ID: fmt.Sprintf("anime:%d", anime.MalID),
|
|
Type: "anime",
|
|
Label: anime.DisplayTitle(),
|
|
Subtitle: strings.TrimSpace("Anime " + anime.Type),
|
|
Href: fmt.Sprintf("/anime/%d", anime.MalID),
|
|
Image: anime.ImageURL(),
|
|
})
|
|
}
|
|
return items
|
|
}
|
|
|
|
func (h *AnimeHandler) commandPalettePersonalItems(c *gin.Context, userID string, query string) []commandPaletteItem {
|
|
items := make([]commandPaletteItem, 0, 5)
|
|
|
|
watchlist, err := h.watchlistSvc.GetCommandPaletteWatchlist(c.Request.Context(), userID, query, 5)
|
|
if err != nil {
|
|
return items
|
|
}
|
|
|
|
for _, entry := range watchlist {
|
|
title := watchlistTitle(entry)
|
|
items = append(items, commandPaletteItem{
|
|
ID: fmt.Sprintf("watchlist:%d", entry.AnimeID),
|
|
Type: "watchlist",
|
|
Label: title,
|
|
Subtitle: watchlistStatusLabel(entry.Status),
|
|
Href: fmt.Sprintf("/anime/%d", entry.AnimeID),
|
|
Image: entry.ImageUrl,
|
|
})
|
|
if len(items) >= 5 {
|
|
return items
|
|
}
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
func (h *AnimeHandler) commandPaletteContinueItems(c *gin.Context, userID string, query string) []commandPaletteItem {
|
|
items := make([]commandPaletteItem, 0, 5)
|
|
|
|
rows, err := h.watchlistSvc.GetCommandPaletteContinueWatching(c.Request.Context(), userID, query, 5)
|
|
if err != nil {
|
|
return items
|
|
}
|
|
|
|
for _, row := range rows {
|
|
title := continueWatchingTitle(row)
|
|
episode := ""
|
|
href := fmt.Sprintf("/anime/%d/watch", row.AnimeID)
|
|
if row.CurrentEpisode.Valid {
|
|
episode = fmt.Sprintf(" episode %d", row.CurrentEpisode.Int64)
|
|
href = fmt.Sprintf("%s?ep=%d", href, row.CurrentEpisode.Int64)
|
|
}
|
|
items = append(items, commandPaletteItem{
|
|
ID: fmt.Sprintf("continue:%d", row.AnimeID),
|
|
Type: "continue",
|
|
Label: "Continue watching " + title,
|
|
Subtitle: "Resume" + episode,
|
|
Href: href,
|
|
Image: row.ImageUrl,
|
|
})
|
|
if len(items) >= 5 {
|
|
return items
|
|
}
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
func commandPaletteMatches(query string, values ...string) bool {
|
|
needle := strings.ToLower(strings.TrimSpace(query))
|
|
for _, value := range values {
|
|
if strings.Contains(strings.ToLower(value), needle) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func continueWatchingTitle(row db.GetContinueWatchingEntriesRow) string {
|
|
if row.TitleEnglish.Valid && row.TitleEnglish.String != "" {
|
|
return row.TitleEnglish.String
|
|
}
|
|
return row.TitleOriginal
|
|
}
|
|
|
|
func watchlistTitle(row domain.UserWatchListRow) string {
|
|
if row.TitleEnglish.Valid && row.TitleEnglish.String != "" {
|
|
return row.TitleEnglish.String
|
|
}
|
|
return row.TitleOriginal
|
|
}
|
|
|
|
func watchlistStatusLabel(status string) string {
|
|
switch status {
|
|
case "watching":
|
|
return "Watching"
|
|
case "plan_to_watch":
|
|
return "Plan to Watch"
|
|
default:
|
|
return "Watchlist"
|
|
}
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleRandomAnime(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
anime, err := h.svc.GetRandomAnime(ctx)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch random anime"})
|
|
return
|
|
}
|
|
if anime.MalID == 0 {
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": "Random anime unavailable"})
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
inWatchlist := false
|
|
if u, ok := user.(*domain.User); ok {
|
|
watchlistMap := h.watchlistMapForIDs(c.Request.Context(), u.ID, []int64{int64(anime.MalID)})
|
|
inWatchlist = watchlistMap[int64(anime.MalID)]
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": anime,
|
|
"in_watchlist": inWatchlist,
|
|
})
|
|
}
|
|
|
|
func (h *AnimeHandler) HandleAnimeReviews(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Param("id"))
|
|
if id <= 0 {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
|
|
reviews, hasNextPage, err := h.svc.GetReviews(c.Request.Context(), id, page)
|
|
if err != nil {
|
|
c.Status(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
user, _ := c.Get("User")
|
|
|
|
if c.GetHeader("HX-Request") == "true" && page > 1 {
|
|
c.HTML(http.StatusOK, "reviews.gohtml", gin.H{
|
|
"_fragment": "review_cards",
|
|
"Reviews": reviews,
|
|
"NextPage": page + 1,
|
|
"HasNextPage": hasNextPage,
|
|
"AnimeID": id,
|
|
})
|
|
return
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "reviews.gohtml", gin.H{
|
|
"CurrentPath": fmt.Sprintf("/anime/%d/reviews", id),
|
|
"Reviews": reviews,
|
|
"NextPage": page + 1,
|
|
"HasNextPage": hasNextPage,
|
|
"AnimeID": id,
|
|
"User": user,
|
|
})
|
|
}
|