Organized the differences between server and docker versions.
We are launching a docker version (server version) today so we want to just organize the repo so its easier to navigate.
This commit is contained in:
166
desktop/src/api/list/list.controller.ts
Normal file
166
desktop/src/api/list/list.controller.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import {FastifyReply, FastifyRequest} from 'fastify';
|
||||
import * as listService from './list.service';
|
||||
|
||||
interface UserRequest extends FastifyRequest {
|
||||
user?: { id: number };
|
||||
}
|
||||
|
||||
interface EntryParams {
|
||||
entryId: string;
|
||||
|
||||
}
|
||||
|
||||
interface SingleEntryQuery {
|
||||
source: string;
|
||||
entry_type: string;
|
||||
}
|
||||
|
||||
export async function getList(req: UserRequest, reply: FastifyReply) {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await listService.getUserList(userId);
|
||||
return { results };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return reply.code(500).send({ error: "Failed to retrieve list" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSingleEntry(req: UserRequest, reply: FastifyReply) {
|
||||
const userId = req.user?.id;
|
||||
const { entryId } = req.params as EntryParams;
|
||||
const { source, entry_type } = req.query as SingleEntryQuery;
|
||||
|
||||
if (!userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!entryId || !source || !entry_type) {
|
||||
return reply.code(400).send({ error: "Missing required identifier: entryId, source, or entry_type." });
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const entry = await listService.getSingleListEntry(
|
||||
userId,
|
||||
entryId,
|
||||
source,
|
||||
entry_type
|
||||
);
|
||||
|
||||
if (!entry) {
|
||||
|
||||
return reply.code(404).send({ found: false, message: "Entry not found in user list." });
|
||||
}
|
||||
|
||||
return { found: true, entry: entry };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return reply.code(500).send({ error: "Failed to retrieve list entry" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertEntry(req: UserRequest, reply: FastifyReply) {
|
||||
const userId = req.user?.id;
|
||||
const body = req.body as any;
|
||||
|
||||
if (!userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!body.entry_id || !body.source || !body.status || !body.entry_type) {
|
||||
return reply.code(400).send({
|
||||
error: "Missing required fields (entry_id, source, status, entry_type)."
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const entryData = {
|
||||
user_id: userId,
|
||||
entry_id: body.entry_id,
|
||||
external_id: body.external_id,
|
||||
source: body.source,
|
||||
entry_type: body.entry_type,
|
||||
status: body.status,
|
||||
progress: body.progress || 0,
|
||||
score: body.score || null,
|
||||
start_date: body.start_date || null,
|
||||
end_date: body.end_date || null,
|
||||
repeat_count: body.repeat_count ?? 0,
|
||||
notes: body.notes || null,
|
||||
is_private: body.is_private ?? 0
|
||||
};
|
||||
|
||||
const result = await listService.upsertListEntry(entryData);
|
||||
|
||||
return { success: true, changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return reply.code(500).send({ error: "Failed to save list entry" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteEntry(req: UserRequest, reply: FastifyReply) {
|
||||
const userId = req.user?.id;
|
||||
const { entryId } = req.params as EntryParams;
|
||||
const { source } = req.query as { source?: string }; // ✅ VIENE DEL FRONT
|
||||
|
||||
if (!userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!entryId || !source) {
|
||||
return reply.code(400).send({ error: "Missing entryId or source." });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await listService.deleteListEntry(
|
||||
userId,
|
||||
entryId,
|
||||
source
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return { success: true, external: result.external };
|
||||
} else {
|
||||
return reply.code(404).send({
|
||||
error: "Entry not found or unauthorized to delete."
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return reply.code(500).send({ error: "Failed to delete list entry" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getListByFilter(req: UserRequest, reply: FastifyReply) {
|
||||
const userId = req.user?.id;
|
||||
const { status, entry_type } = req.query as any;
|
||||
|
||||
if (!userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!status && !entry_type) {
|
||||
return reply.code(400).send({
|
||||
error: "At least one filter is required (status or entry_type)."
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await listService.getUserListByFilter(
|
||||
userId,
|
||||
status,
|
||||
entry_type
|
||||
);
|
||||
|
||||
return { results };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return reply.code(500).send({ error: "Failed to retrieve filtered list" });
|
||||
}
|
||||
}
|
||||
12
desktop/src/api/list/list.routes.ts
Normal file
12
desktop/src/api/list/list.routes.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import * as controller from './list.controller';
|
||||
|
||||
async function listRoutes(fastify: FastifyInstance) {
|
||||
fastify.get('/list', controller.getList);
|
||||
fastify.get('/list/entry/:entryId', controller.getSingleEntry);
|
||||
fastify.post('/list/entry', controller.upsertEntry);
|
||||
fastify.delete('/list/entry/:entryId', controller.deleteEntry);
|
||||
fastify.get('/list/filter', controller.getListByFilter);
|
||||
}
|
||||
|
||||
export default listRoutes;
|
||||
584
desktop/src/api/list/list.service.ts
Normal file
584
desktop/src/api/list/list.service.ts
Normal file
@@ -0,0 +1,584 @@
|
||||
import {queryAll, run, queryOne} from '../../shared/database';
|
||||
import {getExtension} from '../../shared/extensions';
|
||||
import * as animeService from '../anime/anime.service';
|
||||
import * as booksService from '../books/books.service';
|
||||
import * as aniListService from '../anilist/anilist.service';
|
||||
|
||||
interface ListEntryData {
|
||||
entry_type: any;
|
||||
user_id: number;
|
||||
entry_id: number;
|
||||
|
||||
source: string;
|
||||
|
||||
status: string;
|
||||
|
||||
progress: number;
|
||||
score: number | null;
|
||||
}
|
||||
|
||||
const USER_DB = 'userdata';
|
||||
|
||||
export async function upsertListEntry(entry: any) {
|
||||
const {
|
||||
user_id,
|
||||
entry_id,
|
||||
source,
|
||||
entry_type,
|
||||
status,
|
||||
progress,
|
||||
score,
|
||||
start_date,
|
||||
end_date,
|
||||
repeat_count,
|
||||
notes,
|
||||
is_private
|
||||
} = entry;
|
||||
|
||||
let prev: any = 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 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.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserList(userId: number): Promise<any> {
|
||||
const sql = `
|
||||
SELECT * FROM ListEntry
|
||||
WHERE user_id = ?
|
||||
ORDER BY updated_at DESC;
|
||||
`;
|
||||
|
||||
try {
|
||||
const dbList = await queryAll(sql, [userId], USER_DB) as ListEntryData[];
|
||||
const connected = await isConnected(userId);
|
||||
|
||||
let finalList: any[] = [...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: any | null = null;
|
||||
const id = entry.entry_id;
|
||||
const type = entry.entry_type;
|
||||
const ext = getExtension(entry.source);
|
||||
|
||||
try {
|
||||
if (type === 'ANIME') {
|
||||
if(entry.source === 'anilist') {
|
||||
const anime: any = await animeService.getAnimeById(id);
|
||||
contentDetails = {
|
||||
title: anime?.title.english || 'Unknown Anime Title',
|
||||
poster: anime?.coverImage?.extraLarge || '',
|
||||
total_episodes: anime?.episodes || 0,
|
||||
};
|
||||
}
|
||||
else{
|
||||
const anime: any = 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: any = 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: any = 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.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteListEntry(
|
||||
userId: number,
|
||||
entryId: string | number,
|
||||
source: string
|
||||
) {
|
||||
|
||||
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 run(sql, [userId, entryId], USER_DB);
|
||||
return { success: result.changes > 0, changes: result.changes, external: false };
|
||||
}
|
||||
|
||||
export async function getSingleListEntry(
|
||||
userId: number,
|
||||
entryId: string | number,
|
||||
source: string,
|
||||
entryType: string
|
||||
): Promise<any> {
|
||||
|
||||
const localSql = `
|
||||
SELECT * FROM ListEntry
|
||||
WHERE user_id = ? AND entry_id = ? AND source = ? AND entry_type = ?;
|
||||
`;
|
||||
|
||||
const localResult = await queryAll(
|
||||
localSql,
|
||||
[userId, entryId, source, entryType],
|
||||
USER_DB
|
||||
) as any[];
|
||||
|
||||
if (localResult.length > 0) {
|
||||
const entry = localResult[0];
|
||||
|
||||
const contentDetails: any =
|
||||
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 queryOne(sql, [userId], USER_DB) as any;
|
||||
if (!integration?.access_token) return null;
|
||||
if (entryType === 'NOVEL') {entryType = 'MANGA'}
|
||||
|
||||
const aniEntry = await aniListService.getSingleAniListEntry(
|
||||
integration.access_token,
|
||||
Number(entryId),
|
||||
entryType as any
|
||||
);
|
||||
|
||||
if (!aniEntry) return null;
|
||||
|
||||
const contentDetails: any =
|
||||
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;
|
||||
}
|
||||
|
||||
export async function getActiveAccessToken(userId: number): Promise<string | null> {
|
||||
const sql = `
|
||||
SELECT access_token, expires_at
|
||||
FROM UserIntegration
|
||||
WHERE user_id = ? AND platform = 'AniList';
|
||||
`;
|
||||
|
||||
try {
|
||||
const integration = await queryOne(sql, [userId], USER_DB) as any | null;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isConnected(userId: number): Promise<boolean> {
|
||||
const token = await getActiveAccessToken(userId);
|
||||
return !!token;
|
||||
}
|
||||
|
||||
export async function getUserListByFilter(
|
||||
userId: number,
|
||||
status?: string,
|
||||
entryType?: string
|
||||
): Promise<any> {
|
||||
|
||||
let sql = `
|
||||
SELECT * FROM ListEntry
|
||||
WHERE user_id = ?
|
||||
ORDER BY updated_at DESC;
|
||||
`;
|
||||
|
||||
const params: any[] = [userId];
|
||||
|
||||
try {
|
||||
const dbList = await queryAll(sql, params, USER_DB) as ListEntryData[];
|
||||
const connected = await isConnected(userId);
|
||||
|
||||
const statusMap: any = {
|
||||
watching: 'CURRENT',
|
||||
reading: 'CURRENT',
|
||||
completed: 'COMPLETED',
|
||||
paused: 'PAUSED',
|
||||
dropped: 'DROPPED',
|
||||
planning: 'PLANNING'
|
||||
};
|
||||
|
||||
const mappedStatus = status ? statusMap[status.toLowerCase()] : null;
|
||||
|
||||
let finalList: any[] = [];
|
||||
|
||||
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: any[] = [];
|
||||
|
||||
if (connected) {
|
||||
const anilistEntries = await aniListService.getUserAniList(userId);
|
||||
|
||||
filteredAniList = anilistEntries.filter((entry: any) => {
|
||||
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: any | null = null;
|
||||
const id = entry.entry_id;
|
||||
const type = entry.entry_type;
|
||||
const ext = getExtension(entry.source);
|
||||
|
||||
try {
|
||||
if (type === 'ANIME') {
|
||||
const anime: any = 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: any = 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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user