better ui for anilist entries & fixes

This commit is contained in:
2025-12-07 02:24:30 +01:00
parent 6ae823ac0b
commit 1973069949
15 changed files with 1723 additions and 668 deletions

View File

@@ -27,43 +27,60 @@ export async function upsertListEntry(entry: any) {
entry_type,
status,
progress,
score
score,
start_date,
end_date,
repeat_count,
notes,
is_private
} = entry;
if (source === 'anilist') {
const token = await getActiveAccessToken(user_id);
if (token) {
try {
const result = await aniListService.updateAniListEntry(token, {
mediaId: entry_id,
status,
progress,
score
score,
start_date,
end_date,
repeat_count,
notes,
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, updated_at)
(
user_id, entry_id, source, entry_type, status,
progress, score,
start_date, end_date, repeat_count, notes, is_private,
updated_at
)
VALUES
(?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 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;
`;
@@ -74,7 +91,12 @@ export async function upsertListEntry(entry: any) {
entry_type,
status,
progress,
score || null
score ?? null,
start_date || null,
end_date || null,
repeat_count ?? 0,
notes || null,
is_private ?? 0
];
try {
@@ -101,8 +123,6 @@ export async function getUserList(userId: number): Promise<any> {
let finalList: ListEntryData[] = [...dbList];
if (connected) {
const token = await getActiveAccessToken(userId);
const anilistEntries = await aniListService.getUserAniList(userId);
const localWithoutAnilist = dbList.filter(
@@ -184,30 +204,22 @@ export async function getUserList(userId: number): Promise<any> {
}
}
export async function deleteListEntry(userId: number, entryId: string | number) {
export async function deleteListEntry(
userId: number,
entryId: string | number,
source: string
) {
const checkSql = `
SELECT source
FROM ListEntry
WHERE user_id = ? AND entry_id = ?;
`;
if (source === 'anilist') {
const existing = await queryOne(checkSql, [userId, entryId], USER_DB) as any;
if (existing?.source === 'anilist') {
const sql = `
SELECT access_token, anilist_user_id
FROM UserIntegration
WHERE user_id = ? AND platform = 'AniList';
`;
const token = await getActiveAccessToken(userId);
const integration = await queryOne(sql, [userId], USER_DB) as any;
if (integration?.access_token) {
if (token) {
try {
await aniListService.deleteAniListEntry(
integration.access_token,
Number(entryId)
token,
Number(entryId),
);
return { success: true, external: true };
@@ -217,18 +229,14 @@ export async function deleteListEntry(userId: number, entryId: string | number)
}
}
// ✅ SOLO LOCAL
const sql = `
DELETE FROM ListEntry
WHERE user_id = ? AND entry_id = ?;
`;
try {
const result = await run(sql, [userId, entryId], USER_DB);
return { success: result.changes > 0, changes: result.changes, external: false };
} catch (error) {
console.error("Error al eliminar la entrada de lista:", error);
throw new Error("Error en la base de datos al eliminar la entrada.");
}
const result = await run(sql, [userId, entryId], USER_DB);
return { success: result.changes > 0, changes: result.changes, external: false };
}
export async function getSingleListEntry(
@@ -238,19 +246,61 @@ export async function getSingleListEntry(
entryType: string
): Promise<any> {
const connected = await isConnected(userId);
// ✅ 1. BUSCAR PRIMERO EN TU BASE DE DATOS
const localSql = `
SELECT * FROM ListEntry
WHERE user_id = ? AND entry_id = ? AND source = ? AND entry_type = ?;
`;
if (source === 'anilist' && connected) {
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, anilist_user_id
SELECT access_token
FROM UserIntegration
WHERE user_id = ? AND platform = 'AniList';
`;
const integration = await queryOne(sql, [userId], USER_DB) as any;
if (!integration) return null;
if (!integration?.access_token) return null;
const aniEntry = await aniListService.getSingleAniListEntry(
integration.access_token,
@@ -258,118 +308,38 @@ export async function getSingleListEntry(
entryType as any
);
if (!aniEntry) return null;
let contentDetails: any = null;
const contentDetails: any =
entryType === 'ANIME'
? await animeService.getAnimeById(entryId).catch(() => null)
: await booksService.getBookById(entryId).catch(() => null);
if (entryType === 'ANIME') {
const anime:any = await animeService.getAnimeById(entryId);
let finalTitle = contentDetails?.title || 'Unknown';
let finalPoster = contentDetails?.coverImage?.extraLarge ||
contentDetails?.image ||
'https://placehold.co/400x600?text=No+Cover';
contentDetails = {
title: anime?.title,
poster: anime?.coverImage?.extraLarge || anime?.image || '',
total_episodes: anime?.episodes || anime?.nextAiringEpisode?.episode - 1 || 0,
};
} else {
const book: any = await booksService.getBookById(entryId);
contentDetails = {
title: book?.title,
poster: book?.coverImage?.extraLarge || book?.image || '',
total_chapters: book?.chapters || book?.volumes * 10 || 0,
};
}
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';
if (typeof finalTitle === 'object') {
finalTitle =
finalTitle.userPreferred ||
finalTitle.english ||
finalTitle.romaji ||
'Unknown';
}
return {
user_id: userId,
entry_id: Number(entryId),
source: 'anilist',
entry_type: entryType,
status: aniEntry.status,
progress: aniEntry.progress,
score: aniEntry.score,
...aniEntry,
title: finalTitle,
poster: finalPoster,
total_episodes: contentDetails?.total_episodes,
total_chapters: contentDetails?.total_chapters,
total_episodes: contentDetails?.episodes,
total_chapters: contentDetails?.chapters,
};
}
const sql = `
SELECT * FROM ListEntry
WHERE user_id = ? AND entry_id = ? AND source = ? AND entry_type = ?;
`;
const dbEntry = await queryAll(sql, [userId, entryId, source, entryType], USER_DB) as ListEntryData[];
if (!dbEntry || dbEntry.length === 0) return null;
const entry = dbEntry[0];
let contentDetails: any | null = null;
try {
if (entryType === 'ANIME') {
let anime: any;
if (source === 'anilist') {
anime = await animeService.getAnimeById(entryId);
} else {
const ext = getExtension(source);
anime = await animeService.getAnimeInfoExtension(ext, entryId.toString());
}
contentDetails = {
title: anime?.title,
poster: anime?.coverImage?.extraLarge || anime?.image || '',
total_episodes: anime?.episodes || anime?.nextAiringEpisode?.episode - 1 || 0,
};
} else {
let book: any;
if (source === 'anilist') {
book = await booksService.getBookById(entryId);
} else {
const ext = getExtension(source);
book = await booksService.getBookInfoExtension(ext, entryId.toString());
}
contentDetails = {
title: book?.title,
poster: book?.coverImage?.extraLarge || book?.image || '',
total_chapters: book?.chapters || book?.volumes * 10 || 0,
};
}
} catch {
contentDetails = {
title: 'Unknown',
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 null;
}
export async function getActiveAccessToken(userId: number): Promise<string | null> {