"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.upsertListEntry = upsertListEntry; exports.getUserList = getUserList; exports.deleteListEntry = deleteListEntry; exports.getSingleListEntry = getSingleListEntry; exports.getActiveAccessToken = getActiveAccessToken; exports.isConnected = isConnected; exports.getUserListByFilter = getUserListByFilter; const database_1 = require("../../shared/database"); const extensions_1 = require("../../shared/extensions"); const animeService = __importStar(require("../anime/anime.service")); const booksService = __importStar(require("../books/books.service")); const aniListService = __importStar(require("../anilist/anilist.service")); const USER_DB = 'userdata'; async function upsertListEntry(entry) { const { user_id, entry_id, source, entry_type, status, progress, score, start_date, end_date, repeat_count, notes, is_private } = entry; let prev = null; try { prev = await getSingleListEntry(user_id, entry_id, source, entry_type); } catch { prev = null; } const isNew = !prev; if (!isNew && prev?.progress != null && progress < prev.progress) { return { changes: 0, ignored: true }; } const today = new Date().toISOString().slice(0, 10); if (prev?.start_date && !entry.start_date) { entry.start_date = prev.start_date; } if (!prev?.start_date && progress === 1) { entry.start_date = today; } const total = prev?.total_episodes ?? prev?.total_chapters ?? null; if (total && progress >= total) { entry.status = 'COMPLETED'; entry.end_date = today; } if (source === 'anilist') { const token = await getActiveAccessToken(user_id); if (token) { try { const result = await aniListService.updateAniListEntry(token, { mediaId: entry.entry_id, status: entry.status, progress: entry.progress, score: entry.score, start_date: entry.start_date, end_date: entry.end_date, repeat_count: entry.repeat_count, notes: entry.notes, is_private: entry.is_private }); return { changes: 0, external: true, anilistResult: result }; } catch (err) { console.error("Error actualizando AniList:", err); } } } const sql = ` INSERT INTO ListEntry ( user_id, entry_id, source, entry_type, status, progress, score, start_date, end_date, repeat_count, notes, is_private, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(user_id, entry_id) DO UPDATE SET source = EXCLUDED.source, entry_type = EXCLUDED.entry_type, status = EXCLUDED.status, progress = EXCLUDED.progress, score = EXCLUDED.score, start_date = EXCLUDED.start_date, end_date = EXCLUDED.end_date, repeat_count = EXCLUDED.repeat_count, notes = EXCLUDED.notes, is_private = EXCLUDED.is_private, updated_at = CURRENT_TIMESTAMP; `; const params = [ entry.user_id, entry.entry_id, entry.source, entry.entry_type, entry.status, entry.progress, entry.score ?? null, entry.start_date || null, entry.end_date || null, entry.repeat_count ?? 0, entry.notes || null, entry.is_private ?? 0 ]; try { const result = await (0, database_1.run)(sql, params, USER_DB); return { changes: result.changes, lastID: result.lastID, external: false }; } catch (error) { console.error("Error al guardar la entrada de lista:", error); throw new Error("Error en la base de datos al guardar la entrada."); } } async function getUserList(userId) { const sql = ` SELECT * FROM ListEntry WHERE user_id = ? ORDER BY updated_at DESC; `; try { const dbList = await (0, database_1.queryAll)(sql, [userId], USER_DB); const connected = await isConnected(userId); let finalList = [...dbList]; if (connected) { const anilistEntries = await aniListService.getUserAniList(userId); const localWithoutAnilist = dbList.filter(entry => entry.source !== 'anilist'); finalList = [...anilistEntries, ...localWithoutAnilist]; } const enrichedListPromises = finalList.map(async (entry) => { if (entry.source === 'anilist' && connected) { let finalTitle = entry.title; if (typeof finalTitle === 'object' && finalTitle !== null) { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown Title'; } return { ...entry, title: finalTitle, poster: entry.poster || 'https://placehold.co/400x600?text=No+Cover', }; } let contentDetails = null; const id = entry.entry_id; const type = entry.entry_type; const ext = (0, extensions_1.getExtension)(entry.source); try { if (type === 'ANIME') { if (entry.source === 'anilist') { const anime = await animeService.getAnimeById(id); contentDetails = { title: anime?.title.english || 'Unknown Anime Title', poster: anime?.coverImage?.extraLarge || '', total_episodes: anime?.episodes || 0, }; } else { const anime = await animeService.getAnimeInfoExtension(ext, id.toString()); contentDetails = { title: anime?.title || 'Unknown Anime Title', poster: anime?.image || 'https://placehold.co/400x600?text=No+Cover', total_episodes: anime?.episodes || 0, }; } } else if (type === 'MANGA' || type === 'NOVEL') { if (entry.source === 'anilist') { const book = await booksService.getBookById(id); contentDetails = { title: book?.title.english || 'Unknown Book Title', poster: book?.coverImage?.extraLarge || 'https://placehold.co/400x600?text=No+Cover', total_chapters: book?.chapters || book?.volumes * 10 || 0, }; } else { const book = await booksService.getBookInfoExtension(ext, id.toString()); contentDetails = { title: book?.title || 'Unknown Book Title', poster: book?.image || '', total_chapters: book?.chapters || book?.volumes * 10 || 0, }; } } } catch { contentDetails = { title: 'Error Loading Details', poster: 'https://placehold.co/400x600?text=No+Cover', }; } let finalTitle = contentDetails?.title || 'Unknown Title'; let finalPoster = contentDetails?.poster || 'https://placehold.co/400x600?text=No+Cover'; if (typeof finalTitle === 'object' && finalTitle !== null) { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown Title'; } return { ...entry, title: finalTitle, poster: finalPoster, total_episodes: contentDetails?.total_episodes, total_chapters: contentDetails?.total_chapters, }; }); return await Promise.all(enrichedListPromises); } catch (error) { console.error("Error al obtener la lista del usuario:", error); throw new Error("Error getting list."); } } async function deleteListEntry(userId, entryId, source) { if (source === 'anilist') { const token = await getActiveAccessToken(userId); if (token) { try { await aniListService.deleteAniListEntry(token, Number(entryId)); return { success: true, external: true }; } catch (err) { console.error("Error borrando en AniList:", err); } } } const sql = ` DELETE FROM ListEntry WHERE user_id = ? AND entry_id = ?; `; const result = await (0, database_1.run)(sql, [userId, entryId], USER_DB); return { success: result.changes > 0, changes: result.changes, external: false }; } async function getSingleListEntry(userId, entryId, source, entryType) { const localSql = ` SELECT * FROM ListEntry WHERE user_id = ? AND entry_id = ? AND source = ? AND entry_type = ?; `; const localResult = await (0, database_1.queryAll)(localSql, [userId, entryId, source, entryType], USER_DB); if (localResult.length > 0) { const entry = localResult[0]; const contentDetails = entryType === 'ANIME' ? await animeService.getAnimeById(entryId).catch(() => null) : await booksService.getBookById(entryId).catch(() => null); let finalTitle = contentDetails?.title || 'Unknown'; let finalPoster = contentDetails?.coverImage?.extraLarge || contentDetails?.image || 'https://placehold.co/400x600?text=No+Cover'; if (typeof finalTitle === 'object') { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown'; } return { ...entry, title: finalTitle, poster: finalPoster, total_episodes: contentDetails?.episodes, total_chapters: contentDetails?.chapters, }; } if (source === 'anilist') { const connected = await isConnected(userId); if (!connected) return null; const sql = ` SELECT access_token FROM UserIntegration WHERE user_id = ? AND platform = 'AniList'; `; const integration = await (0, database_1.queryOne)(sql, [userId], USER_DB); if (!integration?.access_token) return null; if (entryType === 'NOVEL') { entryType = 'MANGA'; } const aniEntry = await aniListService.getSingleAniListEntry(integration.access_token, Number(entryId), entryType); if (!aniEntry) return null; const contentDetails = entryType === 'ANIME' ? await animeService.getAnimeById(entryId).catch(() => null) : await booksService.getBookById(entryId).catch(() => null); let finalTitle = contentDetails?.title || 'Unknown'; let finalPoster = contentDetails?.coverImage?.extraLarge || contentDetails?.image || 'https://placehold.co/400x600?text=No+Cover'; if (typeof finalTitle === 'object') { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown'; } return { user_id: userId, ...aniEntry, title: finalTitle, poster: finalPoster, total_episodes: contentDetails?.episodes, total_chapters: contentDetails?.chapters, }; } return null; } async function getActiveAccessToken(userId) { const sql = ` SELECT access_token, expires_at FROM UserIntegration WHERE user_id = ? AND platform = 'AniList'; `; try { const integration = await (0, database_1.queryOne)(sql, [userId], USER_DB); if (!integration) { return null; } const expiryDate = new Date(integration.expires_at); const now = new Date(); const fiveMinutes = 5 * 60 * 1000; if (expiryDate.getTime() < (now.getTime() + fiveMinutes)) { console.log(`AniList token for user ${userId} expired or near expiry.`); return null; } return integration.access_token; } catch (error) { console.error("Error al verificar la integración de AniList:", error); return null; } } async function isConnected(userId) { const token = await getActiveAccessToken(userId); return !!token; } async function getUserListByFilter(userId, status, entryType) { let sql = ` SELECT * FROM ListEntry WHERE user_id = ? ORDER BY updated_at DESC; `; const params = [userId]; try { const dbList = await (0, database_1.queryAll)(sql, params, USER_DB); const connected = await isConnected(userId); const statusMap = { watching: 'CURRENT', reading: 'CURRENT', completed: 'COMPLETED', paused: 'PAUSED', dropped: 'DROPPED', planning: 'PLANNING' }; const mappedStatus = status ? statusMap[status.toLowerCase()] : null; let finalList = []; const filteredLocal = dbList.filter((entry) => { if (mappedStatus && entry.status !== mappedStatus) return false; if (entryType) { if (entryType === 'MANGA') { if (!['MANGA', 'NOVEL'].includes(entry.entry_type)) return false; } else { if (entry.entry_type !== entryType) return false; } } return true; }); let filteredAniList = []; if (connected) { const anilistEntries = await aniListService.getUserAniList(userId); filteredAniList = anilistEntries.filter((entry) => { if (mappedStatus && entry.status !== mappedStatus) return false; if (entryType) { if (entryType === 'MANGA') { if (!['MANGA', 'NOVEL'].includes(entry.entry_type)) return false; } else { if (entry.entry_type !== entryType) return false; } } return true; }); } finalList = [...filteredAniList, ...filteredLocal]; const enrichedListPromises = finalList.map(async (entry) => { if (entry.source === 'anilist') { let finalTitle = entry.title; if (typeof finalTitle === 'object' && finalTitle !== null) { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown Title'; } return { ...entry, title: finalTitle, poster: entry.poster || 'https://placehold.co/400x600?text=No+Cover', }; } let contentDetails = null; const id = entry.entry_id; const type = entry.entry_type; const ext = (0, extensions_1.getExtension)(entry.source); try { if (type === 'ANIME') { const anime = await animeService.getAnimeInfoExtension(ext, id.toString()); contentDetails = { title: anime?.title || 'Unknown Anime Title', poster: anime?.image || '', total_episodes: anime?.episodes || 0, }; } else if (type === 'MANGA' || type === 'NOVEL') { const book = await booksService.getBookInfoExtension(ext, id.toString()); contentDetails = { title: book?.title || 'Unknown Book Title', poster: book?.image || '', total_chapters: book?.chapters || book?.volumes * 10 || 0, }; } } catch { contentDetails = { title: 'Error Loading Details', poster: 'https://placehold.co/400x600?text=No+Cover', }; } let finalTitle = contentDetails?.title || 'Unknown Title'; let finalPoster = contentDetails?.poster || 'https://placehold.co/400x600?text=No+Cover'; if (typeof finalTitle === 'object' && finalTitle !== null) { finalTitle = finalTitle.userPreferred || finalTitle.english || finalTitle.romaji || 'Unknown Title'; } return { ...entry, title: finalTitle, poster: finalPoster, total_episodes: contentDetails?.total_episodes, total_chapters: contentDetails?.total_chapters, }; }); return await Promise.all(enrichedListPromises); } catch (error) { console.error("Error al filtrar la lista del usuario:", error); throw new Error("Error en la base de datos al obtener la lista filtrada."); } }