mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 04:26:36 +08:00
112 lines
3.5 KiB
Go
112 lines
3.5 KiB
Go
package reader
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/truewhile/MeBox/internal/model"
|
|
)
|
|
|
|
// 书架分组(对应 legado BookGroup;存储见 model.ReaderBookGroups)。
|
|
//
|
|
// 语义与影视模块的媒体库标签一致:整份替换、一本书只归一个组、组内顺序即展示顺序。
|
|
// 额外的两条收敛放在服务层,因为它们需要知道书籍是否存在:
|
|
// - 只保留该用户书架上的书(书被移出书架后分组里不留死 ID);
|
|
// - 一本书只归一个组(越靠前的分组优先)。
|
|
// 空分组会保留:用户可能就是先建好分组再往里放书。
|
|
|
|
// GetBookGroups 读用户的书架分组。没有分组时返回空切片(非 nil),方便前端直接遍历。
|
|
func (s *ReaderService) GetBookGroups(ctx context.Context, userID string) ([]model.BookGroupSet, error) {
|
|
if userID == "" {
|
|
return []model.BookGroupSet{}, nil
|
|
}
|
|
row, err := s.repo.GetBookGroups(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groups := model.NormalizeBookGroups(row.DecodeBookGroups())
|
|
if len(groups) == 0 {
|
|
return []model.BookGroupSet{}, nil
|
|
}
|
|
owned, err := s.ownedBookIDs(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return filterBookGroups(groups, owned), nil
|
|
}
|
|
|
|
// SetBookGroups 覆盖保存用户的书架分组,落库前做过收敛与归属过滤。
|
|
func (s *ReaderService) SetBookGroups(ctx context.Context, userID string, groups []model.BookGroupSet) ([]model.BookGroupSet, error) {
|
|
if userID == "" {
|
|
return nil, fmt.Errorf("缺少用户信息")
|
|
}
|
|
owned, err := s.ownedBookIDs(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
normalized := dedupeBookGroups(filterBookGroups(model.NormalizeBookGroups(groups), owned))
|
|
raw, err := model.EncodeBookGroups(normalized)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.repo.SaveBookGroups(ctx, &model.ReaderBookGroups{UserID: userID, Groups: raw}); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(normalized) == 0 {
|
|
return []model.BookGroupSet{}, nil
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
// ownedBookIDs 返回该用户书架上的书籍 ID 集合。
|
|
func (s *ReaderService) ownedBookIDs(ctx context.Context, userID string) (map[string]struct{}, error) {
|
|
books, err := s.repo.ListBooks(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make(map[string]struct{}, len(books))
|
|
for i := range books {
|
|
out[books[i].ID] = struct{}{}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// filterBookGroups 只保留 owned 里的书籍 ID;空分组原样保留。
|
|
func filterBookGroups(groups []model.BookGroupSet, owned map[string]struct{}) []model.BookGroupSet {
|
|
if len(groups) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]model.BookGroupSet, 0, len(groups))
|
|
for _, group := range groups {
|
|
ids := make([]string, 0, len(group.BookIDs))
|
|
for _, id := range group.BookIDs {
|
|
if _, ok := owned[id]; ok {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
out = append(out, model.BookGroupSet{Name: group.Name, BookIDs: ids})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// dedupeBookGroups 收敛成「一本书只归一个组」:越靠前的分组优先,后面的重复项被移除。
|
|
func dedupeBookGroups(groups []model.BookGroupSet) []model.BookGroupSet {
|
|
if len(groups) == 0 {
|
|
return nil
|
|
}
|
|
claimed := make(map[string]struct{})
|
|
out := make([]model.BookGroupSet, 0, len(groups))
|
|
for _, group := range groups {
|
|
ids := make([]string, 0, len(group.BookIDs))
|
|
for _, id := range group.BookIDs {
|
|
if _, ok := claimed[id]; ok {
|
|
continue
|
|
}
|
|
claimed[id] = struct{}{}
|
|
ids = append(ids, id)
|
|
}
|
|
out = append(out, model.BookGroupSet{Name: group.Name, BookIDs: ids})
|
|
}
|
|
return out
|
|
}
|