diff --git a/desktop/src/api/anilist/anilist.ts b/desktop/src/api/anilist/anilist.ts index a245daf..c9991d3 100644 --- a/desktop/src/api/anilist/anilist.ts +++ b/desktop/src/api/anilist/anilist.ts @@ -2,58 +2,49 @@ import { FastifyInstance } from "fastify"; import { run } from "../../shared/database"; async function anilist(fastify: FastifyInstance) { - fastify.get("/anilist", async (request, reply) => { + fastify.post("/anilist/store", async (request, reply) => { try { - const { code, state } = request.query as { code?: string; state?: string }; + const { + userId, + accessToken, + tokenType = "Bearer", + expiresIn + } = request.body as { + userId: number; + accessToken: string; + tokenType?: string; + expiresIn?: number; + }; - if (!code) return reply.status(400).send("No code"); - if (!state) return reply.status(400).send("No user state"); - - const userId = Number(state); - if (!userId || isNaN(userId)) { - return reply.status(400).send("Invalid user id"); - } - - const tokenRes = await fetch("https://anilist.co/api/v2/oauth/token", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - grant_type: "authorization_code", - client_id: process.env.ANILIST_CLIENT_ID, - client_secret: process.env.ANILIST_CLIENT_SECRET, - redirect_uri: "http://localhost:54322/api/anilist", - code - }) - }); - - const tokenData = await tokenRes.json(); - - if (!tokenData.access_token) { - console.error("AniList token error:", tokenData); - return reply.status(500).send("Failed to get AniList token"); + if (!userId || !accessToken) { + return reply.status(400).send({ error: "Faltan datos (User ID o Token)" }); } + // 1. Verificar que el token es válido consultando a AniList const userRes = await fetch("https://graphql.anilist.co", { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `${tokenData.token_type} ${tokenData.access_token}` + Authorization: `${tokenType} ${accessToken}` }, body: JSON.stringify({ query: `query { Viewer { id } }` }) }); + if (!userRes.ok) { + return reply.status(401).send({ error: "Token de AniList inválido o expirado" }); + } + const userData = await userRes.json(); const anilistUserId = userData?.data?.Viewer?.id; if (!anilistUserId) { - console.error("AniList Viewer error:", userData); - return reply.status(500).send("Failed to fetch AniList user"); + return reply.status(500).send({ error: "No se pudo obtener el ID de usuario de AniList" }); } const expiresAt = new Date( - Date.now() + tokenData.expires_in * 1000 + Date.now() + 365 * 24 * 60 * 60 * 1000 ).toISOString(); await run( @@ -71,19 +62,19 @@ async function anilist(fastify: FastifyInstance) { [ userId, "AniList", - tokenData.access_token, - tokenData.refresh_token, - tokenData.token_type, + accessToken, + "", // <- aquí + tokenType, anilistUserId, expiresAt ], "userdata" ); - return reply.redirect("http://localhost:54322/?anilist=success"); + return reply.send({ ok: true, anilistUserId }); } catch (e) { console.error("AniList error:", e); - return reply.redirect("http://localhost:54322/?anilist=error"); + return reply.status(500).send({ error: "Error interno del servidor al guardar AniList" }); } }); } diff --git a/desktop/src/scripts/profile.js b/desktop/src/scripts/profile.js index 6a2e3a7..273d6d0 100644 --- a/desktop/src/scripts/profile.js +++ b/desktop/src/scripts/profile.js @@ -122,13 +122,16 @@ const DashboardApp = { headerBadge.title = `Connected as ${data.anilistUserId}`; } if (statusEl) { - statusEl.textContent = `Connected as ID: ${data.anilistUserId}`; - statusEl.style.color = 'var(--color-success)'; + // CAMBIO: Mostrar fecha de expiración si existe + const expiresDate = data.expiresAt ? new Date(data.expiresAt).toLocaleDateString() : 'Unknown'; + statusEl.innerHTML = ` + Connected as: ${data.anilistUserId} + Expires: ${expiresDate} + `; } if (btn) { btn.textContent = 'Disconnect'; btn.className = 'btn-stream-outline link-danger'; - btn.onclick = () => this.disconnectAniList(userId); } } else { @@ -140,7 +143,7 @@ const DashboardApp = { if (btn) { btn.textContent = 'Connect'; btn.className = 'btn-stream-outline'; - btn.onclick = () => this.redirectToAniListLogin(); + btn.onclick = () => this.openAniListModal(); } } }, @@ -154,6 +157,83 @@ const DashboardApp = { window.location.href = `https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&state=${state}`; } catch (err) { console.error(err); alert('Error starting AniList login'); } }, + openAniListModal: function() { + const modal = document.getElementById('anilist-connect-modal'); + const body = document.getElementById('anilist-modal-body'); + const clientId = 32898; // Tu Client ID + + // Generamos el HTML del modal dinámicamente + body.innerHTML = ` + + +
+ + + Open AniList Login ↗ + +
+ +
+ + +
+ + + `; + + modal.classList.remove('hidden'); + }, + + closeAniListModal: function() { + document.getElementById('anilist-connect-modal').classList.add('hidden'); + }, + + submitAniListToken: async function() { + const tokenInput = document.getElementById('manual-anilist-token'); + const token = tokenInput.value.trim(); + const userId = DashboardApp.State.currentUserId; + + if (!token) { + alert('Please paste the AniList token first'); + return; + } + + const confirmBtn = document.querySelector('#anilist-connect-modal .btn-primary'); + const originalText = confirmBtn.textContent; + confirmBtn.textContent = "Verifying..."; + confirmBtn.disabled = true; + + try { + const res = await fetch(`${API_BASE}/anilist/store`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: userId, + accessToken: token + }) + }); + + const data = await res.json(); + + if (!res.ok) throw new Error(data.error || 'Failed to verify token'); + + this.closeAniListModal(); + await this.checkIntegrations(userId); + alert('AniList connected successfully!'); + + } catch (err) { + console.error(err); + alert(err.message || 'Invalid Token'); + } finally { + confirmBtn.textContent = originalText; + confirmBtn.disabled = false; + } + }, disconnectAniList: async function(userId) { if(!confirm("Disconnect AniList?")) return; diff --git a/desktop/src/scripts/users.js b/desktop/src/scripts/users.js index 2d236a2..581ca99 100644 --- a/desktop/src/scripts/users.js +++ b/desktop/src/scripts/users.js @@ -838,8 +838,7 @@ function openAniListModal(userId) { modalUserActions.classList.remove('active'); modalEditUser.classList.remove('active'); - aniListContent.innerHTML = `
Loading integration status...
`; - + // Estado de carga inicial modalAniList.innerHTML = `