mirror of
https://github.com/sbondCo/Watcharr.git
synced 2026-08-07 07:14:44 +00:00
346 lines
10 KiB
Go
346 lines
10 KiB
Go
package imprt
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/sbondCo/Watcharr/database/entity"
|
|
"github.com/sbondCo/Watcharr/domain"
|
|
"github.com/sbondCo/Watcharr/feature/watched"
|
|
"github.com/sbondCo/Watcharr/feature/watched/episode"
|
|
"github.com/sbondCo/Watcharr/feature/watched/season"
|
|
"github.com/sbondCo/Watcharr/media/tmdb"
|
|
"github.com/sbondCo/Watcharr/util"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type WatchedProvider interface {
|
|
AddWatched(userId uint, ar domain.WatchedAddRequest, extraProps domain.WatchedAddExtraProps) (entity.Watched, error)
|
|
GetWatchedItemByTmdbId(userId uint, tmdbId uint, contentType entity.ContentType) (entity.Watched, error)
|
|
}
|
|
|
|
type WatchedSeasonProvider interface {
|
|
AddWatchedSeason(userId uint, ar season.WatchedSeasonAddRequest) (season.WatchedSeasonAddResponse, error)
|
|
}
|
|
|
|
type WatchedEpisodeProvider interface {
|
|
AddWatchedEpisodes(userId uint, ar episode.WatchedEpisodeAddRequest) (episode.WatchedEpisodeAddResponse, error)
|
|
}
|
|
|
|
type TagProvider interface {
|
|
AddTag(userId uint, tr domain.TagAddRequest) (entity.Tag, error)
|
|
GetTagByNameAndColor(userId uint, tagName string, tagColor string, tagBgColor string) (entity.Tag, error)
|
|
}
|
|
|
|
type SearchProvider interface {
|
|
Search(r domain.SearchRequest, pp util.PaginationParams, userId uint) (domain.SearchResponse, error)
|
|
}
|
|
|
|
type Service struct {
|
|
db *gorm.DB
|
|
wp WatchedProvider
|
|
wsp WatchedSeasonProvider
|
|
wep WatchedEpisodeProvider
|
|
tmdb *tmdb.TMDB
|
|
activityProvider domain.ActivityAddProvider
|
|
tagProvider TagProvider
|
|
searchProvider SearchProvider
|
|
}
|
|
|
|
func NewService(
|
|
db *gorm.DB,
|
|
wp WatchedProvider,
|
|
wsp WatchedSeasonProvider,
|
|
wep WatchedEpisodeProvider,
|
|
tmdb *tmdb.TMDB,
|
|
activityProvider domain.ActivityAddProvider,
|
|
tagProvider TagProvider,
|
|
searchProvider SearchProvider,
|
|
) *Service {
|
|
return &Service{
|
|
db,
|
|
wp,
|
|
wsp,
|
|
wep,
|
|
tmdb,
|
|
activityProvider,
|
|
tagProvider,
|
|
searchProvider,
|
|
}
|
|
}
|
|
|
|
func (s *Service) ImportContent(
|
|
userId uint,
|
|
ar domain.ImportRequest,
|
|
) (domain.ImportResponse, error) {
|
|
slog.Debug("import: Processing request:", "request", ar)
|
|
|
|
// If we have a TMDB ID given to us, we can go directly to
|
|
// `SuccessfulImport` and let AddWatched fail if it doesn't exist.
|
|
if ar.TmdbID != 0 {
|
|
switch ar.Type {
|
|
case domain.ImportContentTypeMovie:
|
|
return s.SuccessfulImport(userId, &ar, domain.SuccessfulImportProps{
|
|
TmdbID: ar.TmdbID,
|
|
ContentType: util.SupportedMediaMovie,
|
|
}), nil
|
|
case domain.ImportContentTypeShow:
|
|
return s.SuccessfulImport(userId, &ar, domain.SuccessfulImportProps{
|
|
TmdbID: ar.TmdbID,
|
|
ContentType: util.SupportedMediaShow,
|
|
}), nil
|
|
}
|
|
// Unsupported types will continue to below...
|
|
}
|
|
|
|
// If imdb id passed, attempt to get content with it
|
|
if ar.ImdbID != "" && (ar.Type == domain.ImportContentTypeMovie ||
|
|
ar.Type == domain.ImportContentTypeShow ||
|
|
ar.Type == domain.ImportContentTypeShowEpisode) {
|
|
// Try import with imdb id.
|
|
resp, err := s.importWithIMDBID(userId, &ar)
|
|
if err == nil || !errors.Is(err, ErrNoResult) {
|
|
return resp, err
|
|
}
|
|
// If ErrNoResult then we allow going below to search by name.
|
|
}
|
|
|
|
// If igdb provided, go straight to SuccessfulImport with it.
|
|
if ar.IgdbID != 0 && (ar.Type == domain.ImportContentTypeGame) {
|
|
return s.SuccessfulImport(userId, &ar, domain.SuccessfulImportProps{
|
|
IgdbID: ar.IgdbID,
|
|
ContentType: util.SupportedMediaGame,
|
|
}), nil
|
|
}
|
|
|
|
// If we have no IDs, run importWithName, which searches for content
|
|
// by name.
|
|
return s.importWithName(userId, &ar)
|
|
}
|
|
|
|
func (s *Service) SuccessfulImport(
|
|
userId uint,
|
|
ar *domain.ImportRequest,
|
|
props domain.SuccessfulImportProps,
|
|
) domain.ImportResponse {
|
|
status := entity.FINISHED
|
|
if ar.Status != "" {
|
|
status = ar.Status
|
|
}
|
|
// Get the latest date from DatesWatched if we have any.
|
|
var wDate time.Time
|
|
if len(ar.DatesWatched) > 0 {
|
|
for _, dw := range ar.DatesWatched {
|
|
if dw.After(wDate) {
|
|
wDate = dw
|
|
}
|
|
}
|
|
}
|
|
// Build WatchedAddRequest
|
|
wAddReq := domain.WatchedAddRequest{
|
|
ContentType: props.ContentType,
|
|
Status: status,
|
|
Rating: ar.Rating,
|
|
Thoughts: ar.Thoughts,
|
|
WatchedDate: wDate,
|
|
}
|
|
switch props.ContentType {
|
|
case util.SupportedMediaMovie, util.SupportedMediaShow:
|
|
wAddReq.TMDBID = props.TmdbID
|
|
case util.SupportedMediaGame:
|
|
wAddReq.IGDBID = props.IgdbID
|
|
default:
|
|
slog.Error("successfulImport: Invalid contentType provided!",
|
|
"content_type", props.ContentType)
|
|
return domain.ImportResponse{Type: domain.IMPORT_FAILED}
|
|
}
|
|
// Running add watched
|
|
w, err := s.wp.AddWatched(
|
|
userId,
|
|
wAddReq,
|
|
domain.WatchedAddExtraProps{
|
|
ActivityType: entity.IMPORTED_WATCHED,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, domain.ErrWatchedExists) {
|
|
slog.Error("successfulImport: Must already be on watch list",
|
|
"error", err)
|
|
return domain.ImportResponse{Type: domain.IMPORT_EXISTS}
|
|
}
|
|
slog.Error("successfulImport: Failed to add content as watched",
|
|
"error", err)
|
|
return domain.ImportResponse{Type: domain.IMPORT_FAILED}
|
|
}
|
|
// Add activity of the original time the show was added to the users
|
|
// watchlist on whichever platform they are coming from.
|
|
if ar.RatingCustomDate != nil {
|
|
var addedActivity entity.Activity
|
|
if len(w.Activity) > 0 {
|
|
activityJson, _ := json.Marshal(map[string]any{
|
|
"rating": ar.Rating,
|
|
"linkedActivity": w.Activity[0].ID,
|
|
})
|
|
addedActivity, _ = s.activityProvider.AddActivity(
|
|
userId,
|
|
domain.ActivityAddProps{
|
|
WatchedID: w.ID,
|
|
Type: entity.IMPORTED_RATING,
|
|
Data: string(activityJson),
|
|
CustomDate: ar.RatingCustomDate,
|
|
},
|
|
false,
|
|
)
|
|
} else {
|
|
addedActivity, _ = s.activityProvider.AddActivity(
|
|
userId,
|
|
domain.ActivityAddProps{
|
|
WatchedID: w.ID,
|
|
Type: entity.IMPORTED_RATING,
|
|
Data: strconv.Itoa(int(ar.Rating)),
|
|
CustomDate: ar.RatingCustomDate,
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
w.Activity = append(w.Activity, addedActivity)
|
|
}
|
|
// Add all dates watched as activity, if any
|
|
if len(ar.DatesWatched) > 0 {
|
|
for i, v := range ar.DatesWatched {
|
|
countAsPlay := true
|
|
if i == 0 && ar.Status == entity.FINISHED {
|
|
// If the watched status we are importing is of FINISHED
|
|
// then the first DatesWatched must not count as a play,
|
|
// since the import activity (set in AddWatched) will already.
|
|
// Any subsequent DatesWatched should count as a play though.
|
|
countAsPlay = false
|
|
slog.Info("successfulImport: Set countAsPlay=false for first" +
|
|
"DatesWatched to avoid duplicate play count with AddWatched activity.")
|
|
}
|
|
customDate := v
|
|
addedActivity, err := s.activityProvider.AddActivity(
|
|
userId,
|
|
domain.ActivityAddProps{
|
|
WatchedID: w.ID,
|
|
Type: entity.IMPORTED_ADDED_WATCHED,
|
|
CustomDate: &customDate,
|
|
},
|
|
countAsPlay,
|
|
)
|
|
if err == nil {
|
|
w.Activity = append(w.Activity, addedActivity)
|
|
} else {
|
|
slog.Error("successfulImport: Failed to add dateswatched activity.",
|
|
"date", v, "error", err)
|
|
}
|
|
}
|
|
}
|
|
// Add all activity passed in.
|
|
// Probably was is a Watcharr export being imported, so it'll have all it's activity too.
|
|
if len(ar.Activity) > 0 {
|
|
slog.Debug("successfulImport: Importing activity")
|
|
for i, v := range ar.Activity {
|
|
activityDate := ar.Activity[i].CustomDate
|
|
if activityDate == nil || activityDate.IsZero() {
|
|
activityDate = &ar.Activity[i].CreatedAt
|
|
}
|
|
addedActivity, err := s.activityProvider.AddActivity(
|
|
userId,
|
|
domain.ActivityAddProps{
|
|
WatchedID: w.ID,
|
|
Type: v.Type,
|
|
Data: v.Data,
|
|
CustomDate: activityDate,
|
|
},
|
|
v.CountAsPlay,
|
|
)
|
|
if err == nil {
|
|
w.Activity = append(w.Activity, addedActivity)
|
|
} else {
|
|
slog.Error("successfulImport: Failed to add imported activity.",
|
|
"full_object", v, "error", err)
|
|
}
|
|
}
|
|
}
|
|
// Import watched seasons, if any
|
|
if len(ar.WatchedSeason) > 0 {
|
|
slog.Debug("successfulImport: Importing watched seasons")
|
|
for _, v := range ar.WatchedSeason {
|
|
ws, err := s.wsp.AddWatchedSeason(userId, season.WatchedSeasonAddRequest{
|
|
WatchedID: w.ID,
|
|
SeasonNumber: v.SeasonNumber,
|
|
Status: v.Status,
|
|
Rating: v.Rating,
|
|
AddActivityDate: v.CreatedAt,
|
|
})
|
|
if err != nil {
|
|
slog.Error("successfulImport: Failed to add watched season.",
|
|
"error", err)
|
|
continue
|
|
}
|
|
w.WatchedSeasons = ws.WatchedSeasons
|
|
}
|
|
}
|
|
// Import watched episodes, if any
|
|
if len(ar.WatchedEpisodes) > 0 {
|
|
slog.Debug("successfulImport: Importing watched episodes")
|
|
for _, v := range ar.WatchedEpisodes {
|
|
ws, err := s.wep.AddWatchedEpisodes(userId, episode.WatchedEpisodeAddRequest{
|
|
WatchedID: w.ID,
|
|
SeasonNumber: v.SeasonNumber,
|
|
EpisodeNumber: v.EpisodeNumber,
|
|
Status: v.Status,
|
|
Rating: v.Rating,
|
|
AddActivityDate: v.CreatedAt,
|
|
})
|
|
if err != nil {
|
|
slog.Error("successfulImport: Failed to add watched episodes.",
|
|
"error", err)
|
|
continue
|
|
}
|
|
w.WatchedEpisodes = ws.WatchedEpisodes
|
|
}
|
|
}
|
|
// Import tags, if any
|
|
if len(ar.Tags) > 0 {
|
|
// Create tags if they dont exist
|
|
slog.Debug("successfulImport: Importing tags")
|
|
for _, v := range ar.Tags {
|
|
// Check if tag exists
|
|
var t entity.Tag
|
|
t, err := s.tagProvider.GetTagByNameAndColor(userId, v.Name, v.Color, v.BgColor)
|
|
if err != nil && err.Error() != "tag does not exist" {
|
|
slog.Error("successfulImport: Failed to check for an existing tag",
|
|
"name", v.Name, "error", err)
|
|
continue
|
|
}
|
|
if t.ID == 0 {
|
|
tag, err := s.tagProvider.AddTag(userId, domain.TagAddRequest{
|
|
Name: v.Name,
|
|
Color: v.Color,
|
|
BgColor: v.BgColor,
|
|
})
|
|
if err != nil {
|
|
slog.Error("successfulImport: Failed to add a tag.",
|
|
"name", v.Name, "error", err)
|
|
continue
|
|
}
|
|
t = tag
|
|
}
|
|
|
|
// Associate the watched entry with the tag
|
|
err = watched.AddWatchedToTag(s.db, userId, t.ID, w.ID)
|
|
if err != nil {
|
|
slog.Error("successfulImport: Failed to associate watched entry with tag.",
|
|
"error", err)
|
|
continue
|
|
}
|
|
w.Tags = append(w.Tags, t)
|
|
}
|
|
}
|
|
return domain.ImportResponse{Type: domain.IMPORT_SUCCESS, WatchedEntry: w}
|
|
}
|