This commit is contained in:
truewhile
2026-08-28 22:20:01 +08:00
parent e365250440
commit c30dab56a3
7 changed files with 222 additions and 23 deletions
+67
View File
@@ -4,14 +4,81 @@ import (
"context"
"github.com/ShukeBta/MMTL/internal/model"
"github.com/ShukeBta/MMTL/internal/repository"
"gorm.io/gorm"
)
type LibraryPreviewItem struct {
model.Library
Total int64 `json:"total"`
Cards []SeriesCard `json:"cards"`
}
// ListLibraries returns every library configured on the server.
func (s *MediaService) ListLibraries(ctx context.Context) ([]model.Library, error) {
return s.repo.Library.List(ctx)
}
// ListLibrariesWithPreview returns libraries populated with item counts and latest preview cards.
func (s *MediaService) ListLibrariesWithPreview(ctx context.Context, libraries []model.Library, visibility MediaVisibility, cardLimit int) ([]LibraryPreviewItem, error) {
if cardLimit <= 0 {
cardLimit = 10
}
out := make([]LibraryPreviewItem, len(libraries))
if len(libraries) == 0 {
return out, nil
}
libIDs := make([]string, 0, len(libraries))
for i, lib := range libraries {
out[i] = LibraryPreviewItem{
Library: lib,
Total: 0,
Cards: []SeriesCard{},
}
libIDs = append(libIDs, lib.ID)
}
visibility = ExpandMediaVisibilityForMergedCloudLibraries(ctx, s.repo, visibility)
filter := repository.MediaQueryFilter{
IncludeNSFW: visibility.IncludeNSFW,
AllowedLibraryIDs: visibility.AllowedLibraryIDs,
HiddenLibraryIDs: visibility.HiddenLibraryIDs,
}
counts, err := s.repo.Media.CountByLibraries(ctx, libIDs, filter)
if err != nil {
return nil, err
}
for i := range out {
if total, ok := counts[out[i].ID]; ok {
out[i].Total = total
}
}
for i := range out {
if out[i].Total == 0 {
continue
}
items, _, err := s.repo.Media.ListByLibrariesFiltered(ctx, []string{out[i].ID}, 0, 60, filter)
if err != nil {
continue
}
s.attachLibraryMetadata(ctx, items)
cards := groupMediaSeriesCards(items)
if len(cards) > cardLimit {
cards = cards[:cardLimit]
}
if cards == nil {
cards = []SeriesCard{}
}
out[i].Cards = cards
}
return out, nil
}
// DeleteLibrary removes a library and its media rows. The on-disk files are
// left untouched.
func (s *MediaService) DeleteLibrary(ctx context.Context, id string) error {
+94
View File
@@ -0,0 +1,94 @@
package service
import (
"fmt"
"testing"
"time"
"go.uber.org/zap"
"github.com/ShukeBta/MMTL/internal/config"
"github.com/ShukeBta/MMTL/internal/model"
"github.com/ShukeBta/MMTL/internal/repository"
)
func TestListLibrariesWithPreview(t *testing.T) {
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
repos := repository.New(db)
lib1 := model.Library{Name: "电影", Path: "/media/movies", Type: "movie", Enabled: true}
if err := repos.Library.Create(t.Context(), &lib1); err != nil {
t.Fatal(err)
}
lib2 := model.Library{Name: "动漫", Path: "/media/anime", Type: "anime", Enabled: true}
if err := repos.Library.Create(t.Context(), &lib2); err != nil {
t.Fatal(err)
}
now := time.Date(2026, 7, 2, 12, 0, 0, 0, time.UTC)
var rows []model.Media
// Add 5 movies to lib1
for i := 1; i <= 5; i++ {
rows = append(rows, model.Media{
Base: model.Base{ID: fmt.Sprintf("movie-%02d", i), CreatedAt: now.Add(time.Duration(i) * time.Hour)},
LibraryID: lib1.ID,
Title: fmt.Sprintf("电影%d", i),
Path: fmt.Sprintf("/media/movies/电影%d/movie%d.mp4", i, i),
PosterURL: fmt.Sprintf("/api/media/movie-%02d/poster", i),
})
}
// Add 12 episodes of 1 anime to lib2
for i := 1; i <= 12; i++ {
rows = append(rows, model.Media{
Base: model.Base{ID: fmt.Sprintf("anime-ep-%02d", i), CreatedAt: now.Add(time.Duration(i) * time.Minute)},
LibraryID: lib2.ID,
Title: fmt.Sprintf("某动漫 第%d集", i),
Path: fmt.Sprintf("/media/anime/某动漫/Season 01/某动漫.S01E%02d.mp4", i),
SeasonNum: 1,
EpisodeNum: i,
PosterURL: "/api/media/anime-01/poster",
})
}
if err := repos.DB.Create(&rows).Error; err != nil {
t.Fatal(err)
}
svc := NewMediaService(&config.Config{}, zap.NewNop(), repos)
previews, err := svc.ListLibrariesWithPreview(t.Context(), []model.Library{lib1, lib2}, MediaVisibility{IncludeNSFW: true}, 10)
if err != nil {
t.Fatalf("ListLibrariesWithPreview failed: %v", err)
}
if len(previews) != 2 {
t.Fatalf("got %d previews, want 2", len(previews))
}
// Verify lib1 (movies)
if previews[0].ID != lib1.ID {
t.Errorf("preview[0].ID = %q, want %q", previews[0].ID, lib1.ID)
}
if previews[0].Total != 5 {
t.Errorf("preview[0].Total = %d, want 5", previews[0].Total)
}
if len(previews[0].Cards) != 5 {
t.Errorf("preview[0].Cards count = %d, want 5", len(previews[0].Cards))
}
// Verify lib2 (anime)
if previews[1].ID != lib2.ID {
t.Errorf("preview[1].ID = %q, want %q", previews[1].ID, lib2.ID)
}
if previews[1].Total != 12 {
t.Errorf("preview[1].Total = %d, want 12", previews[1].Total)
}
// 12 episodes should be grouped into 1 SeriesCard with Count = 12
if len(previews[1].Cards) != 1 {
t.Errorf("preview[1].Cards count = %d, want 1", len(previews[1].Cards))
} else if previews[1].Cards[0].Count != 12 {
t.Errorf("preview[1].Cards[0].Count = %d, want 12", previews[1].Cards[0].Count)
}
}