refactor: domain anime type

This commit is contained in:
2026-05-26 22:45:16 +02:00
parent 5a054d250e
commit e9576d7584
6 changed files with 62 additions and 37 deletions

View File

@@ -22,6 +22,14 @@ type AnimeHandler struct {
watchlistSvc domain.WatchlistService watchlistSvc domain.WatchlistService
} }
func wrapAnimes(in []jikan.Anime) []domain.Anime {
out := make([]domain.Anime, 0, len(in))
for _, a := range in {
out = append(out, domain.Anime{Anime: a})
}
return out
}
func NewAnimeHandler(svc domain.AnimeService, watchlistSvc domain.WatchlistService) *AnimeHandler { func NewAnimeHandler(svc domain.AnimeService, watchlistSvc domain.WatchlistService) *AnimeHandler {
return &AnimeHandler{ return &AnimeHandler{
svc: svc, svc: svc,
@@ -405,7 +413,8 @@ func (h *AnimeHandler) HandleBrowse(c *gin.Context) {
if u, ok := user.(*domain.User); ok { if u, ok := user.(*domain.User); ok {
userID = u.ID userID = u.ID
} }
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, res.Animes) animes := wrapAnimes(res.Animes)
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, animes)
studioName := "" studioName := ""
if studioID > 0 { if studioID > 0 {
@@ -417,10 +426,10 @@ func (h *AnimeHandler) HandleBrowse(c *gin.Context) {
if c.GetHeader("HX-Request") == "true" && page > 1 { if c.GetHeader("HX-Request") == "true" && page > 1 {
c.HTML(http.StatusOK, "browse.gohtml", gin.H{ c.HTML(http.StatusOK, "browse.gohtml", gin.H{
"_fragment": "anime_card_scroll", "_fragment": "anime_card_scroll",
"Animes": res.Animes, "Animes": animes,
"NextPage": page + 1, "NextPage": page + 1,
"HasNextPage": res.HasNextPage, "HasNextPage": res.HasNextPage,
"Query": q, "Query": q,
"Type": animeType, "Type": animeType,
"Status": status, "Status": status,
@@ -450,9 +459,9 @@ func (h *AnimeHandler) HandleBrowse(c *gin.Context) {
"Studio": studioID, "Studio": studioID,
"StudioName": studioName, "StudioName": studioName,
"SFW": sfw, "SFW": sfw,
"GenresList": genresList, "GenresList": genresList,
"Animes": res.Animes, "Animes": animes,
"HasNextPage": res.HasNextPage, "HasNextPage": res.HasNextPage,
"NextPage": page + 1, "NextPage": page + 1,
"User": user, "User": user,
"WatchlistMap": watchlistMap, "WatchlistMap": watchlistMap,
@@ -472,8 +481,8 @@ func (h *AnimeHandler) HandleBrowse(c *gin.Context) {
"StudioName": studioName, "StudioName": studioName,
"SFW": sfw, "SFW": sfw,
"GenresList": genresList, "GenresList": genresList,
"Animes": res.Animes, "Animes": animes,
"HasNextPage": res.HasNextPage, "HasNextPage": res.HasNextPage,
"NextPage": page + 1, "NextPage": page + 1,
"User": user, "User": user,
"WatchlistMap": watchlistMap, "WatchlistMap": watchlistMap,
@@ -634,7 +643,8 @@ func (h *AnimeHandler) HandleQuickSearch(c *gin.Context) {
if u, ok := user.(*domain.User); ok { if u, ok := user.(*domain.User); ok {
userID = u.ID userID = u.ID
} }
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, res.Animes) animes := wrapAnimes(res.Animes)
watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, animes)
type quickSearchResult struct { type quickSearchResult struct {
ID int `json:"id"` ID int `json:"id"`
@@ -645,8 +655,8 @@ func (h *AnimeHandler) HandleQuickSearch(c *gin.Context) {
InWatchlist bool `json:"in_watchlist"` InWatchlist bool `json:"in_watchlist"`
} }
output := make([]quickSearchResult, len(res.Animes)) output := make([]quickSearchResult, len(animes))
for i, anime := range res.Animes { for i, anime := range animes {
output[i] = quickSearchResult{ output[i] = quickSearchResult{
ID: anime.MalID, ID: anime.MalID,
Title: anime.DisplayTitle(), Title: anime.DisplayTitle(),
@@ -736,8 +746,9 @@ func (h *AnimeHandler) commandPaletteAnimeResults(c *gin.Context, query string)
return nil return nil
} }
items := make([]commandPaletteItem, 0, len(res.Animes)) animes := wrapAnimes(res.Animes)
for _, anime := range res.Animes { items := make([]commandPaletteItem, 0, len(animes))
for _, anime := range animes {
items = append(items, commandPaletteItem{ items = append(items, commandPaletteItem{
ID: fmt.Sprintf("anime:%d", anime.MalID), ID: fmt.Sprintf("anime:%d", anime.MalID),
Type: "anime", Type: "anime",

View File

@@ -21,6 +21,14 @@ type animeService struct {
repo domain.AnimeRepository repo domain.AnimeRepository
} }
func wrapAnimes(in []jikan.Anime) []domain.Anime {
out := make([]domain.Anime, 0, len(in))
for _, a := range in {
out = append(out, domain.Anime{Anime: a})
}
return out
}
func NewAnimeService(jikan *jikan.Client, repo domain.AnimeRepository) domain.AnimeService { func NewAnimeService(jikan *jikan.Client, repo domain.AnimeRepository) domain.AnimeService {
return &animeService{jikan: jikan, repo: repo} return &animeService{jikan: jikan, repo: repo}
} }
@@ -56,7 +64,7 @@ func (s *animeService) GetCatalogSection(ctx context.Context, userID string, sec
return domain.CatalogSectionData{}, err return domain.CatalogSectionData{}, err
} }
animes := res.Animes animes := wrapAnimes(res.Animes)
if len(animes) > 6 { if len(animes) > 6 {
animes = animes[:6] animes = animes[:6]
} }
@@ -89,7 +97,7 @@ func (s *animeService) GetDiscoverSection(ctx context.Context, userID string, se
return domain.DiscoverSectionData{}, err return domain.DiscoverSectionData{}, err
} }
animes := res.Animes animes := wrapAnimes(res.Animes)
if len(animes) > 8 { if len(animes) > 8 {
animes = animes[:8] animes = animes[:8]
} }
@@ -197,7 +205,7 @@ func (s *animeService) GetDiscoverForYou(ctx context.Context, userID string) (do
) )
continue continue
} }
animes = append(animes, anime) animes = append(animes, domain.Anime{Anime: anime})
} }
return domain.DiscoverSectionData{Animes: animes}, nil return domain.DiscoverSectionData{Animes: animes}, nil
@@ -247,7 +255,7 @@ func (s *animeService) GetAiringSchedule(ctx context.Context, userID string) ([]
return fetchErr return fetchErr
} }
mu.Lock() mu.Lock()
animes = append(animes, anime) animes = append(animes, domain.Anime{Anime: anime})
mu.Unlock() mu.Unlock()
return nil return nil
}) })
@@ -271,7 +279,11 @@ func (s *animeService) GetAiringSchedule(ctx context.Context, userID string) ([]
} }
func (s *animeService) GetAnimeByID(ctx context.Context, id int) (domain.Anime, error) { func (s *animeService) GetAnimeByID(ctx context.Context, id int) (domain.Anime, error) {
return s.jikan.GetAnimeByID(ctx, id) anime, err := s.jikan.GetAnimeByID(ctx, id)
if err != nil {
return domain.Anime{}, err
}
return domain.Anime{Anime: anime}, nil
} }
func (s *animeService) SearchAdvanced(ctx context.Context, q, animeType, status, orderBy, sort string, genres []int, studioID int, sfw bool, page, limit int) (jikan.SearchResult, error) { func (s *animeService) SearchAdvanced(ctx context.Context, q, animeType, status, orderBy, sort string, genres []int, studioID int, sfw bool, page, limit int) (jikan.SearchResult, error) {
@@ -341,7 +353,7 @@ func (s *animeService) GetRandomAnime(ctx context.Context) (domain.Anime, error)
anime, err := s.jikan.GetRandomAnime(randomCtx) anime, err := s.jikan.GetRandomAnime(randomCtx)
if err == nil { if err == nil {
return anime, nil return domain.Anime{Anime: anime}, nil
} }
for _, fallback := range []func(context.Context, int) (jikan.TopAnimeResult, error){ for _, fallback := range []func(context.Context, int) (jikan.TopAnimeResult, error){
@@ -350,12 +362,12 @@ func (s *animeService) GetRandomAnime(ctx context.Context) (domain.Anime, error)
s.jikan.GetSeasonsUpcoming, s.jikan.GetSeasonsUpcoming,
} { } {
res, fallbackErr := fallback(ctx, 1) res, fallbackErr := fallback(ctx, 1)
if fallbackErr != nil || len(res.Animes) == 0 { if fallbackErr != nil || len(res.Animes) == 0 {
continue continue
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return domain.Anime{Anime: res.Animes[r.Intn(len(res.Animes))]}, nil
} }
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return res.Animes[r.Intn(len(res.Animes))], nil
}
return domain.Anime{}, err return domain.Anime{}, err
} }

View File

@@ -6,7 +6,9 @@ import (
"mal/internal/db" "mal/internal/db"
) )
type Anime = jikan.Anime type Anime struct {
jikan.Anime
}
type TopAnimeResult = jikan.TopAnimeResult type TopAnimeResult = jikan.TopAnimeResult
type Genre = jikan.Genre type Genre = jikan.Genre
type Character = jikan.CharacterEntry type Character = jikan.CharacterEntry

View File

@@ -94,7 +94,7 @@ func (s *EpisodeService) RefreshTrackedDue(ctx context.Context, limit int) error
) )
continue continue
} }
if _, err := s.refresh(ctx, anime); err != nil { if _, err := s.refresh(ctx, domain.Anime{Anime: anime}); err != nil {
observability.Warn( observability.Warn(
"episodes_refresh_failed", "episodes_refresh_failed",
"episodes", "episodes",

View File

@@ -29,7 +29,7 @@ func TestMergeEpisodesUsesUnionAndSynthesizesProviderOnlyEntries(t *testing.T) {
} }
func TestNextBroadcastAfterUsesJikanTimezone(t *testing.T) { func TestNextBroadcastAfterUsesJikanTimezone(t *testing.T) {
anime := domain.Anime{MalID: 1} anime := domain.Anime{Anime: jikan.Anime{MalID: 1}}
anime.Broadcast.Day = "Saturdays" anime.Broadcast.Day = "Saturdays"
anime.Broadcast.Time = "23:00" anime.Broadcast.Time = "23:00"
anime.Broadcast.Timezone = "Asia/Tokyo" anime.Broadcast.Timezone = "Asia/Tokyo"
@@ -44,7 +44,7 @@ func TestNextBroadcastAfterUsesJikanTimezone(t *testing.T) {
} }
func TestNextRetryTimeWithinAndAfterRetryWindow(t *testing.T) { func TestNextRetryTimeWithinAndAfterRetryWindow(t *testing.T) {
anime := domain.Anime{MalID: 1} anime := domain.Anime{Anime: jikan.Anime{MalID: 1}}
anime.Broadcast.Day = "Saturdays" anime.Broadcast.Day = "Saturdays"
anime.Broadcast.Time = "12:00" anime.Broadcast.Time = "12:00"
anime.Broadcast.Timezone = "UTC" anime.Broadcast.Timezone = "UTC"

View File

@@ -143,7 +143,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
} }
} }
canonicalEpisodes, err := s.episodes.GetCanonicalEpisodes(ctx, anime, false) canonicalEpisodes, err := s.episodes.GetCanonicalEpisodes(ctx, domain.Anime{Anime: anime}, false)
if err != nil { if err != nil {
return domain.WatchPageData{}, fmt.Errorf("failed to fetch episodes: %w", err) return domain.WatchPageData{}, fmt.Errorf("failed to fetch episodes: %w", err)
} }
@@ -289,12 +289,12 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
Segments: segments, Segments: segments,
} }
return domain.WatchPageData{ return domain.WatchPageData{
WatchData: watchData, WatchData: watchData,
Anime: anime, Anime: domain.Anime{Anime: anime},
Episodes: canonicalEpisodes.Episodes, Episodes: canonicalEpisodes.Episodes,
CurrentEpID: episode, CurrentEpID: episode,
WatchlistStatus: watchlistStatus, WatchlistStatus: watchlistStatus,
WatchlistIDs: watchlistIDs, WatchlistIDs: watchlistIDs,
Seasons: seasons, Seasons: seasons,
}, nil }, nil