50 changed files with 968 additions and 144 deletions
@ -0,0 +1,236 @@ |
|||||||
|
import { conf } from "@/setup/config"; |
||||||
|
|
||||||
|
import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw"; |
||||||
|
import { |
||||||
|
ExternalIdMovieSearchResult, |
||||||
|
TMDBContentTypes, |
||||||
|
TMDBEpisodeShort, |
||||||
|
TMDBExternalIds, |
||||||
|
TMDBMediaResult, |
||||||
|
TMDBMovieData, |
||||||
|
TMDBMovieExternalIds, |
||||||
|
TMDBMovieResponse, |
||||||
|
TMDBMovieResult, |
||||||
|
TMDBSeason, |
||||||
|
TMDBSeasonMetaResult, |
||||||
|
TMDBShowData, |
||||||
|
TMDBShowExternalIds, |
||||||
|
TMDBShowResponse, |
||||||
|
TMDBShowResult, |
||||||
|
} from "./types/tmdb"; |
||||||
|
import { mwFetch } from "../helpers/fetch"; |
||||||
|
|
||||||
|
export function mediaTypeToTMDB(type: MWMediaType): TMDBContentTypes { |
||||||
|
if (type === MWMediaType.MOVIE) return "movie"; |
||||||
|
if (type === MWMediaType.SERIES) return "show"; |
||||||
|
throw new Error("unsupported type"); |
||||||
|
} |
||||||
|
|
||||||
|
export function TMDBMediaToMediaType(type: string): MWMediaType { |
||||||
|
if (type === "movie") return MWMediaType.MOVIE; |
||||||
|
if (type === "show") return MWMediaType.SERIES; |
||||||
|
throw new Error("unsupported type"); |
||||||
|
} |
||||||
|
|
||||||
|
export function formatTMDBMeta( |
||||||
|
media: TMDBMediaResult, |
||||||
|
season?: TMDBSeasonMetaResult |
||||||
|
): MWMediaMeta { |
||||||
|
const type = TMDBMediaToMediaType(media.object_type); |
||||||
|
let seasons: undefined | MWSeasonMeta[]; |
||||||
|
if (type === MWMediaType.SERIES) { |
||||||
|
seasons = media.seasons |
||||||
|
?.sort((a, b) => a.season_number - b.season_number) |
||||||
|
.map( |
||||||
|
(v): MWSeasonMeta => ({ |
||||||
|
title: v.title, |
||||||
|
id: v.id.toString(), |
||||||
|
number: v.season_number, |
||||||
|
}) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
title: media.title, |
||||||
|
id: media.id.toString(), |
||||||
|
year: media.original_release_year?.toString(), |
||||||
|
poster: media.poster, |
||||||
|
type, |
||||||
|
seasons: seasons as any, |
||||||
|
seasonData: season |
||||||
|
? ({ |
||||||
|
id: season.id.toString(), |
||||||
|
number: season.season_number, |
||||||
|
title: season.title, |
||||||
|
episodes: season.episodes |
||||||
|
.sort((a, b) => a.episode_number - b.episode_number) |
||||||
|
.map((v) => ({ |
||||||
|
id: v.id.toString(), |
||||||
|
number: v.episode_number, |
||||||
|
title: v.title, |
||||||
|
})), |
||||||
|
} as any) |
||||||
|
: (undefined as any), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function TMDBMediaToId(media: MWMediaMeta): string { |
||||||
|
return ["tmdb", mediaTypeToTMDB(media.type), media.id].join("-"); |
||||||
|
} |
||||||
|
|
||||||
|
export function decodeTMDBId( |
||||||
|
paramId: string |
||||||
|
): { id: string; type: MWMediaType } | null { |
||||||
|
const [prefix, type, id] = paramId.split("-", 3); |
||||||
|
if (prefix !== "tmdb") return null; |
||||||
|
let mediaType; |
||||||
|
try { |
||||||
|
mediaType = TMDBMediaToMediaType(type); |
||||||
|
} catch { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return { |
||||||
|
type: mediaType, |
||||||
|
id, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
const baseURL = "https://api.themoviedb.org/3"; |
||||||
|
|
||||||
|
const headers = { |
||||||
|
accept: "application/json", |
||||||
|
Authorization: `Bearer ${conf().TMDB_API_KEY}`, |
||||||
|
}; |
||||||
|
|
||||||
|
async function get<T>(url: string, params?: object): Promise<T> { |
||||||
|
const res = await mwFetch<any>(encodeURI(url), { |
||||||
|
headers, |
||||||
|
baseURL, |
||||||
|
params: { |
||||||
|
...params, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
export async function searchMedia( |
||||||
|
query: string, |
||||||
|
type: TMDBContentTypes |
||||||
|
): Promise<TMDBMovieResponse | TMDBShowResponse> { |
||||||
|
let data; |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case "movie": |
||||||
|
data = await get<TMDBMovieResponse>("search/movie", { |
||||||
|
query, |
||||||
|
include_adult: false, |
||||||
|
language: "en-US", |
||||||
|
page: 1, |
||||||
|
}); |
||||||
|
break; |
||||||
|
case "show": |
||||||
|
data = await get<TMDBShowResponse>("search/tv", { |
||||||
|
query, |
||||||
|
include_adult: false, |
||||||
|
language: "en-US", |
||||||
|
page: 1, |
||||||
|
}); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new Error("Invalid media type"); |
||||||
|
} |
||||||
|
|
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
export async function getMediaDetails(id: string, type: TMDBContentTypes) { |
||||||
|
let data; |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case "movie": |
||||||
|
data = await get<TMDBMovieData>(`/movie/${id}`); |
||||||
|
break; |
||||||
|
case "show": |
||||||
|
data = await get<TMDBShowData>(`/tv/${id}`); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new Error("Invalid media type"); |
||||||
|
} |
||||||
|
|
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
export function getMediaPoster(posterPath: string | null): string | undefined { |
||||||
|
if (posterPath) return `https://image.tmdb.org/t/p/w185/${posterPath}`; |
||||||
|
} |
||||||
|
|
||||||
|
export async function getEpisodes( |
||||||
|
id: string, |
||||||
|
season: number |
||||||
|
): Promise<TMDBEpisodeShort[]> { |
||||||
|
const data = await get<TMDBSeason>(`/tv/${id}/season/${season}`); |
||||||
|
return data.episodes.map((e) => ({ |
||||||
|
id: e.id, |
||||||
|
episode_number: e.episode_number, |
||||||
|
title: e.name, |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
export async function getExternalIds( |
||||||
|
id: string, |
||||||
|
type: TMDBContentTypes |
||||||
|
): Promise<TMDBExternalIds> { |
||||||
|
let data; |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case "movie": |
||||||
|
data = await get<TMDBMovieExternalIds>(`/movie/${id}/external_ids`); |
||||||
|
break; |
||||||
|
case "show": |
||||||
|
data = await get<TMDBShowExternalIds>(`/tv/${id}/external_ids`); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new Error("Invalid media type"); |
||||||
|
} |
||||||
|
|
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
export async function getMovieFromExternalId( |
||||||
|
imdbId: string |
||||||
|
): Promise<string | undefined> { |
||||||
|
const data = await get<ExternalIdMovieSearchResult>(`/find/${imdbId}`, { |
||||||
|
external_source: "imdb_id", |
||||||
|
}); |
||||||
|
|
||||||
|
const movie = data.movie_results[0]; |
||||||
|
if (!movie) return undefined; |
||||||
|
|
||||||
|
return movie.id.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
export function formatTMDBSearchResult( |
||||||
|
result: TMDBShowResult | TMDBMovieResult, |
||||||
|
mediatype: TMDBContentTypes |
||||||
|
): TMDBMediaResult { |
||||||
|
const type = TMDBMediaToMediaType(mediatype); |
||||||
|
if (type === MWMediaType.SERIES) { |
||||||
|
const show = result as TMDBShowResult; |
||||||
|
return { |
||||||
|
title: show.name, |
||||||
|
poster: getMediaPoster(show.poster_path), |
||||||
|
id: show.id, |
||||||
|
original_release_year: new Date(show.first_air_date).getFullYear(), |
||||||
|
object_type: mediatype, |
||||||
|
}; |
||||||
|
} |
||||||
|
const movie = result as TMDBMovieResult; |
||||||
|
|
||||||
|
return { |
||||||
|
title: movie.title, |
||||||
|
poster: getMediaPoster(movie.poster_path), |
||||||
|
id: movie.id, |
||||||
|
original_release_year: new Date(movie.release_date).getFullYear(), |
||||||
|
object_type: mediatype, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
export type JWContentTypes = "movie" | "show"; |
||||||
|
|
||||||
|
export type JWSearchQuery = { |
||||||
|
content_types: JWContentTypes[]; |
||||||
|
page: number; |
||||||
|
page_size: number; |
||||||
|
query: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export type JWPage<T> = { |
||||||
|
items: T[]; |
||||||
|
page: number; |
||||||
|
page_size: number; |
||||||
|
total_pages: number; |
||||||
|
total_results: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export const JW_API_BASE = "https://apis.justwatch.com"; |
||||||
|
export const JW_IMAGE_BASE = "https://images.justwatch.com"; |
||||||
|
|
||||||
|
export type JWSeasonShort = { |
||||||
|
title: string; |
||||||
|
id: number; |
||||||
|
season_number: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export type JWEpisodeShort = { |
||||||
|
title: string; |
||||||
|
id: number; |
||||||
|
episode_number: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export type JWMediaResult = { |
||||||
|
title: string; |
||||||
|
poster?: string; |
||||||
|
id: number; |
||||||
|
original_release_year?: number; |
||||||
|
jw_entity_id: string; |
||||||
|
object_type: JWContentTypes; |
||||||
|
seasons?: JWSeasonShort[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export type JWSeasonMetaResult = { |
||||||
|
title: string; |
||||||
|
id: string; |
||||||
|
season_number: number; |
||||||
|
episodes: JWEpisodeShort[]; |
||||||
|
}; |
@ -0,0 +1,308 @@ |
|||||||
|
export type TMDBContentTypes = "movie" | "show"; |
||||||
|
|
||||||
|
export type TMDBSeasonShort = { |
||||||
|
title: string; |
||||||
|
id: number; |
||||||
|
season_number: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export type TMDBEpisodeShort = { |
||||||
|
title: string; |
||||||
|
id: number; |
||||||
|
episode_number: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export type TMDBMediaResult = { |
||||||
|
title: string; |
||||||
|
poster?: string; |
||||||
|
id: number; |
||||||
|
original_release_year?: number; |
||||||
|
object_type: TMDBContentTypes; |
||||||
|
seasons?: TMDBSeasonShort[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export type TMDBSeasonMetaResult = { |
||||||
|
title: string; |
||||||
|
id: string; |
||||||
|
season_number: number; |
||||||
|
episodes: TMDBEpisodeShort[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export interface TMDBShowData { |
||||||
|
adult: boolean; |
||||||
|
backdrop_path: string | null; |
||||||
|
created_by: { |
||||||
|
id: number; |
||||||
|
credit_id: string; |
||||||
|
name: string; |
||||||
|
gender: number; |
||||||
|
profile_path: string | null; |
||||||
|
}[]; |
||||||
|
episode_run_time: number[]; |
||||||
|
first_air_date: string; |
||||||
|
genres: { |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
homepage: string; |
||||||
|
id: number; |
||||||
|
in_production: boolean; |
||||||
|
languages: string[]; |
||||||
|
last_air_date: string; |
||||||
|
last_episode_to_air: { |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
overview: string; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
air_date: string; |
||||||
|
episode_number: number; |
||||||
|
production_code: string; |
||||||
|
runtime: number | null; |
||||||
|
season_number: number; |
||||||
|
show_id: number; |
||||||
|
still_path: string | null; |
||||||
|
} | null; |
||||||
|
name: string; |
||||||
|
next_episode_to_air: { |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
overview: string; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
air_date: string; |
||||||
|
episode_number: number; |
||||||
|
production_code: string; |
||||||
|
runtime: number | null; |
||||||
|
season_number: number; |
||||||
|
show_id: number; |
||||||
|
still_path: string | null; |
||||||
|
} | null; |
||||||
|
networks: { |
||||||
|
id: number; |
||||||
|
logo_path: string; |
||||||
|
name: string; |
||||||
|
origin_country: string; |
||||||
|
}[]; |
||||||
|
number_of_episodes: number; |
||||||
|
number_of_seasons: number; |
||||||
|
origin_country: string[]; |
||||||
|
original_language: string; |
||||||
|
original_name: string; |
||||||
|
overview: string; |
||||||
|
popularity: number; |
||||||
|
poster_path: string | null; |
||||||
|
production_companies: { |
||||||
|
id: number; |
||||||
|
logo_path: string | null; |
||||||
|
name: string; |
||||||
|
origin_country: string; |
||||||
|
}[]; |
||||||
|
production_countries: { |
||||||
|
iso_3166_1: string; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
seasons: { |
||||||
|
air_date: string; |
||||||
|
episode_count: number; |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
overview: string; |
||||||
|
poster_path: string | null; |
||||||
|
season_number: number; |
||||||
|
}[]; |
||||||
|
spoken_languages: { |
||||||
|
english_name: string; |
||||||
|
iso_639_1: string; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
status: string; |
||||||
|
tagline: string; |
||||||
|
type: string; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBMovieData { |
||||||
|
adult: boolean; |
||||||
|
backdrop_path: string | null; |
||||||
|
belongs_to_collection: { |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
poster_path: string | null; |
||||||
|
backdrop_path: string | null; |
||||||
|
} | null; |
||||||
|
budget: number; |
||||||
|
genres: { |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
homepage: string | null; |
||||||
|
id: number; |
||||||
|
imdb_id: string | null; |
||||||
|
original_language: string; |
||||||
|
original_title: string; |
||||||
|
overview: string | null; |
||||||
|
popularity: number; |
||||||
|
poster_path: string | null; |
||||||
|
production_companies: { |
||||||
|
id: number; |
||||||
|
logo_path: string | null; |
||||||
|
name: string; |
||||||
|
origin_country: string; |
||||||
|
}[]; |
||||||
|
production_countries: { |
||||||
|
iso_3166_1: string; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
release_date: string; |
||||||
|
revenue: number; |
||||||
|
runtime: number | null; |
||||||
|
spoken_languages: { |
||||||
|
english_name: string; |
||||||
|
iso_639_1: string; |
||||||
|
name: string; |
||||||
|
}[]; |
||||||
|
status: string; |
||||||
|
tagline: string | null; |
||||||
|
title: string; |
||||||
|
video: boolean; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBEpisodeResult { |
||||||
|
season: number; |
||||||
|
number: number; |
||||||
|
title: string; |
||||||
|
ids: { |
||||||
|
trakt: number; |
||||||
|
tvdb: number; |
||||||
|
imdb: string; |
||||||
|
tmdb: number; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBShowResult { |
||||||
|
adult: boolean; |
||||||
|
backdrop_path: string | null; |
||||||
|
genre_ids: number[]; |
||||||
|
id: number; |
||||||
|
origin_country: string[]; |
||||||
|
original_language: string; |
||||||
|
original_name: string; |
||||||
|
overview: string; |
||||||
|
popularity: number; |
||||||
|
poster_path: string | null; |
||||||
|
first_air_date: string; |
||||||
|
name: string; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBShowResponse { |
||||||
|
page: number; |
||||||
|
results: TMDBShowResult[]; |
||||||
|
total_pages: number; |
||||||
|
total_results: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBMovieResult { |
||||||
|
adult: boolean; |
||||||
|
backdrop_path: string | null; |
||||||
|
genre_ids: number[]; |
||||||
|
id: number; |
||||||
|
original_language: string; |
||||||
|
original_title: string; |
||||||
|
overview: string; |
||||||
|
popularity: number; |
||||||
|
poster_path: string | null; |
||||||
|
release_date: string; |
||||||
|
title: string; |
||||||
|
video: boolean; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBMovieResponse { |
||||||
|
page: number; |
||||||
|
results: TMDBMovieResult[]; |
||||||
|
total_pages: number; |
||||||
|
total_results: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBEpisode { |
||||||
|
air_date: string; |
||||||
|
episode_number: number; |
||||||
|
id: number; |
||||||
|
name: string; |
||||||
|
overview: string; |
||||||
|
production_code: string; |
||||||
|
runtime: number; |
||||||
|
season_number: number; |
||||||
|
show_id: number; |
||||||
|
still_path: string | null; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
crew: any[]; |
||||||
|
guest_stars: any[]; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBSeason { |
||||||
|
_id: string; |
||||||
|
air_date: string; |
||||||
|
episodes: TMDBEpisode[]; |
||||||
|
name: string; |
||||||
|
overview: string; |
||||||
|
id: number; |
||||||
|
poster_path: string | null; |
||||||
|
season_number: number; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBShowExternalIds { |
||||||
|
id: number; |
||||||
|
imdb_id: null | string; |
||||||
|
freebase_mid: null | string; |
||||||
|
freebase_id: null | string; |
||||||
|
tvdb_id: number; |
||||||
|
tvrage_id: null | string; |
||||||
|
wikidata_id: null | string; |
||||||
|
facebook_id: null | string; |
||||||
|
instagram_id: null | string; |
||||||
|
twitter_id: null | string; |
||||||
|
} |
||||||
|
|
||||||
|
export interface TMDBMovieExternalIds { |
||||||
|
id: number; |
||||||
|
imdb_id: null | string; |
||||||
|
wikidata_id: null | string; |
||||||
|
facebook_id: null | string; |
||||||
|
instagram_id: null | string; |
||||||
|
twitter_id: null | string; |
||||||
|
} |
||||||
|
|
||||||
|
export type TMDBExternalIds = TMDBShowExternalIds | TMDBMovieExternalIds; |
||||||
|
|
||||||
|
export interface ExternalIdMovieSearchResult { |
||||||
|
movie_results: { |
||||||
|
adult: boolean; |
||||||
|
backdrop_path: string; |
||||||
|
id: number; |
||||||
|
title: string; |
||||||
|
original_language: string; |
||||||
|
original_title: string; |
||||||
|
overview: string; |
||||||
|
poster_path: string; |
||||||
|
media_type: string; |
||||||
|
genre_ids: number[]; |
||||||
|
popularity: number; |
||||||
|
release_date: string; |
||||||
|
video: boolean; |
||||||
|
vote_average: number; |
||||||
|
vote_count: number; |
||||||
|
}[]; |
||||||
|
person_results: any[]; |
||||||
|
tv_results: any[]; |
||||||
|
tv_episode_results: any[]; |
||||||
|
tv_season_results: any[]; |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
import { getLegacyMetaFromId } from "@/backend/metadata/getmeta"; |
||||||
|
import { getMovieFromExternalId } from "@/backend/metadata/tmdb"; |
||||||
|
import { MWMediaType } from "@/backend/metadata/types/mw"; |
||||||
|
|
||||||
|
import { WatchedStoreData } from "../types"; |
||||||
|
|
||||||
|
async function migrateId( |
||||||
|
id: number, |
||||||
|
type: MWMediaType |
||||||
|
): Promise<string | undefined> { |
||||||
|
const meta = await getLegacyMetaFromId(type, id.toString()); |
||||||
|
|
||||||
|
if (!meta) return undefined; |
||||||
|
const { tmdbId, imdbId } = meta; |
||||||
|
if (!tmdbId && !imdbId) return undefined; |
||||||
|
|
||||||
|
// movies always have an imdb id on tmdb
|
||||||
|
if (imdbId && type === MWMediaType.MOVIE) { |
||||||
|
const movieId = await getMovieFromExternalId(imdbId); |
||||||
|
if (movieId) return movieId; |
||||||
|
} |
||||||
|
|
||||||
|
if (tmdbId) { |
||||||
|
return tmdbId; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export async function migrateV2Bookmarks(old: any) { |
||||||
|
const oldData = old; |
||||||
|
if (!oldData) return; |
||||||
|
|
||||||
|
const updatedBookmarks = oldData.bookmarks.map( |
||||||
|
async (item: { id: number; type: MWMediaType }) => ({ |
||||||
|
...item, |
||||||
|
id: await migrateId(item.id, item.type), |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
return { |
||||||
|
bookmarks: (await Promise.all(updatedBookmarks)).filter((item) => item.id), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export async function migrateV3Videos(old: any) { |
||||||
|
const oldData = old; |
||||||
|
if (!oldData) return; |
||||||
|
|
||||||
|
const updatedItems = await Promise.all( |
||||||
|
oldData.items.map(async (item: any) => { |
||||||
|
const migratedId = await migrateId( |
||||||
|
item.item.meta.id, |
||||||
|
item.item.meta.type |
||||||
|
); |
||||||
|
|
||||||
|
const migratedItem = { |
||||||
|
...item, |
||||||
|
item: { |
||||||
|
...item.item, |
||||||
|
meta: { |
||||||
|
...item.item.meta, |
||||||
|
id: migratedId, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
...item, |
||||||
|
item: migratedId ? migratedItem : item.item, |
||||||
|
}; |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
const newData: WatchedStoreData = { |
||||||
|
items: updatedItems.map((item) => item.item), |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
...oldData, |
||||||
|
items: newData.items, |
||||||
|
}; |
||||||
|
} |
Loading…
Reference in new issue