package ctrl
import (
"context"
"errors"
"fmt"
"sort"
"time"
"github.com/samber/lo"
"github.com/trim21/errgo"
"go.uber.org/zap"
"github.com/bangumi/server/dal/query"
"github.com/bangumi/server/domain/gerr"
"github.com/bangumi/server/internal/auth"
"github.com/bangumi/server/internal/collections/domain/collection"
"github.com/bangumi/server/internal/episode"
"github.com/bangumi/server/internal/model"
"github.com/bangumi/server/internal/pkg/generic/set"
"github.com/bangumi/server/internal/pkg/generic/slice"
"github.com/bangumi/server/internal/pkg/logger/log"
"github.com/bangumi/server/internal/subject"
)
func (ctl Ctrl) UpdateEpisodesCollection(
ctx context.Context,
u auth.Auth,
subjectID model.SubjectID,
episodeIDs []model.EpisodeID,
t collection.EpisodeCollection,
) error {
if _, err := ctl.subjectCached.Get(ctx, subjectID, subject.Filter{}); err != nil {
return err
}
GORM v1.25.0 起修复了一个 bug,但是被当成 feature 使用了。在该版本之前,Limit 0 认为不是合法的 Limit,会被从 SQL 语句中忽略
see PR: go-gorm/gorm/pull/6191
因此这里需要传入 -1 作为 Limit,从而返回全部数据。GORM 会对负数过过滤,不会出现在最终的 SQL 中。
*/
episodes, err := ctl.episode.List(ctx, subjectID, episode.Filter{}, -1, 0)
if err != nil {
return errgo.Wrap(err, "episodeRepo.List")
}
eIDs := set.FromSlice(slice.Map(episodes, episode.Episode.GetID))
for _, d := range episodeIDs {
if !eIDs.Has(d) {
return fmt.Errorf("%w: episode %d is not episodes of subject %d", ErrInvalidInput, d, subjectID)
}
}
err = ctl.tx.Transaction(ctl.updateEpisodesCollectionTx(ctx, u, subjectID, episodeIDs, t, time.Now()))
if err != nil {
return err
}
episodes = lo.Filter(episodes, func(item episode.Episode, _ int) bool {
return item.Type == episode.TypeNormal && lo.Contains(episodeIDs, item.ID)
})
if len(episodes) == 0 {
return nil
}
sort.Slice(episodes, func(i, j int) bool {
return !episodes[i].Less(episodes[j])
})
e := episodes[0]
s, err := ctl.subjectCached.Get(ctx, e.SubjectID, subject.Filter{})
if err != nil {
return err
}
if t == 0 {
return nil
}
err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e, t)
return errgo.Wrap(err, "timeline.ChangeEpisodeStatus")
}
func (ctl Ctrl) UpdateEpisodeCollection(
ctx context.Context,
u auth.Auth,
episodeID model.EpisodeID,
t collection.EpisodeCollection,
) error {
ctl.log.Info("try to update episode collection info", log.User(u.ID), zap.Uint32("episode", episodeID))
e, err := ctl.episode.Get(ctx, episodeID)
if err != nil {
if errors.Is(err, gerr.ErrNotFound) {
return gerr.ErrEpisodeNotFound
}
return errgo.Wrap(err, "episode.Get")
}
err = ctl.tx.Transaction(
ctl.updateEpisodesCollectionTx(ctx, u, e.SubjectID, []model.EpisodeID{episodeID}, t, time.Now()),
)
if err != nil {
return err
}
s, err := ctl.subjectCached.Get(ctx, e.SubjectID, subject.Filter{})
if err != nil {
return err
}
if t == 0 {
return nil
}
err = ctl.timeline.ChangeEpisodeStatus(ctx, u, s, e, t)
return errgo.Wrap(err, "timeline.ChangeEpisodeStatus")
}
func (ctl Ctrl) updateEpisodesCollectionTx(
ctx context.Context,
u auth.Auth,
subjectID model.SubjectID,
episodeIDs []model.EpisodeID,
t collection.EpisodeCollection,
at time.Time,
) func(tx *query.Query) error {
return func(tx *query.Query) error {
collectionTx := ctl.collection.WithQuery(tx)
sc, err := collectionTx.GetSubjectCollection(ctx, u.ID, subjectID)
if err != nil {
if errors.Is(err, gerr.ErrNotFound) {
return gerr.ErrSubjectNotCollected
}
return errgo.Wrap(err, "collection.GetSubjectCollection")
}
ec, err := collectionTx.UpdateEpisodeCollection(ctx, u.ID, subjectID, episodeIDs, t, at)
if err != nil {
return errgo.Wrap(err, "UpdateEpisodeCollection")
}
epStatus := len(ec)
err = collectionTx.UpdateSubjectCollection(ctx, u.ID,
model.Subject{ID: subjectID, TypeID: sc.SubjectType}, at, "",
func(ctx context.Context, s *collection.Subject) (*collection.Subject, error) {
s.UpdateEps(uint32(epStatus))
return s, nil
})
if err != nil {
ctl.log.Error("failed to update user collection info", zap.Error(err))
return errgo.Wrap(err, "collectionRepo.UpdateSubjectCollection")
}
return nil
}
}