diff --git a/server.js b/server.js index a71cec3..4d80bb2 100644 --- a/server.js +++ b/server.js @@ -29,6 +29,7 @@ fastify.addHook("preHandler", async (request) => { const token = auth.replace("Bearer ", ""); request.user = jwt.verify(token, process.env.JWT_SECRET); } catch (e) { + return reply.code(401).send({ error: "Invalid token" }); } }); diff --git a/src/api/gallery/gallery.controller.ts b/src/api/gallery/gallery.controller.ts index 760bf28..b1e4af0 100644 --- a/src/api/gallery/gallery.controller.ts +++ b/src/api/gallery/gallery.controller.ts @@ -59,22 +59,25 @@ export async function getInfo(req: any, reply: FastifyReply) { } } -export async function getFavorites(req: FastifyRequest, reply: FastifyReply) { +export async function getFavorites(req: any, reply: FastifyReply) { try { - const favorites = await galleryService.getFavorites(); + if (!req.user) return reply.code(401).send({ error: "Unauthorized" }); + + const favorites = await galleryService.getFavorites(req.user.id); return { favorites }; } catch (err) { - const error = err as Error; - console.error("Get Favorites Error:", error.message); + console.error("Get Favorites Error:", (err as Error).message); return reply.code(500).send({ error: "Failed to retrieve favorites" }); } } -export async function getFavoriteById(req: FastifyRequest, reply: FastifyReply) { +export async function getFavoriteById(req: any, reply: FastifyReply) { try { + if (!req.user) return reply.code(401).send({ error: "Unauthorized" }); + const { id } = req.params as { id: string }; - const favorite = await galleryService.getFavoriteById(id); + const favorite = await galleryService.getFavoriteById(id, req.user.id); if (!favorite) { return reply.code(404).send({ error: "Favorite not found" }); @@ -83,25 +86,26 @@ export async function getFavoriteById(req: FastifyRequest, reply: FastifyReply) return { favorite }; } catch (err) { - const error = err as Error; - console.error("Get Favorite By ID Error:", error.message); + console.error("Get Favorite By ID Error:", (err as Error).message); return reply.code(500).send({ error: "Failed to retrieve favorite" }); } } - -export async function addFavorite(req: FastifyRequest<{ Body: AddFavoriteBody }>, reply: FastifyReply) { +export async function addFavorite(req: any, reply: FastifyReply) { try { - const {id, title, image_url, thumbnail_url, tags, provider, headers} = req.body; + if (!req.user) return reply.code(401).send({ error: "Unauthorized" }); + + const { id, title, image_url, thumbnail_url, tags, provider, headers } = req.body; if (!id || !title || !image_url || !thumbnail_url) { return reply.code(400).send({ - error: "Missing required fields: id, title, image_url, thumbnail_url" + error: "Missing required fields" }); } const result = await galleryService.addFavorite({ id, + user_id: req.user.id, title, image_url, thumbnail_url, @@ -122,15 +126,13 @@ export async function addFavorite(req: FastifyRequest<{ Body: AddFavoriteBody }> } } -export async function removeFavorite(req: FastifyRequest<{ Params: RemoveFavoriteParams }>, reply: FastifyReply) { +export async function removeFavorite(req: any, reply: FastifyReply) { try { + if (!req.user) return reply.code(401).send({ error: "Unauthorized" }); + const { id } = req.params; - if (!id) { - return reply.code(400).send({ error: "Missing favorite ID" }); - } - - const result = await galleryService.removeFavorite(id); + const result = await galleryService.removeFavorite(id, req.user.id); if (result.success) { return { success: true, message: "Favorite removed successfully" }; @@ -138,8 +140,7 @@ export async function removeFavorite(req: FastifyRequest<{ Params: RemoveFavorit return reply.code(404).send({ error: "Favorite not found" }); } } catch (err) { - const error = err as Error; - console.error("Remove Favorite Error:", error.message); + console.error("Remove Favorite Error:", (err as Error).message); return reply.code(500).send({ error: "Failed to remove favorite" }); } } \ No newline at end of file diff --git a/src/api/gallery/gallery.service.ts b/src/api/gallery/gallery.service.ts index d17fe0b..efe8718 100644 --- a/src/api/gallery/gallery.service.ts +++ b/src/api/gallery/gallery.service.ts @@ -102,28 +102,32 @@ export async function searchInExtension(providerName: string, query: string, pag } } -export async function getFavorites(): Promise { +export async function getFavorites(userId: number): Promise { const db = getDatabase("favorites"); return new Promise((resolve) => { - db.all('SELECT * FROM favorites', [], (err: Error | null, rows: Favorite[]) => { - if (err) { - console.error('Error getting favorites:', err.message); - resolve([]); - } else { - resolve(rows); + db.all( + 'SELECT * FROM favorites WHERE user_id = ?', + [userId], + (err: Error | null, rows: Favorite[]) => { + if (err) { + console.error('Error getting favorites:', err.message); + resolve([]); + } else { + resolve(rows); + } } - }); + ); }); } -export async function getFavoriteById(id: string): Promise { +export async function getFavoriteById(id: string, userId: number): Promise { const db = getDatabase("favorites"); return new Promise((resolve) => { db.get( - 'SELECT * FROM favorites WHERE id = ?', - [id], + 'SELECT * FROM favorites WHERE id = ? AND user_id = ?', + [id, userId], (err: Error | null, row: Favorite | undefined) => { if (err) { console.error('Error getting favorite by id:', err.message); @@ -136,19 +140,20 @@ export async function getFavoriteById(id: string): Promise { }); } -export async function addFavorite(fav: Favorite): Promise { +export async function addFavorite(fav: Favorite & { user_id: number }): Promise { const db = getDatabase("favorites"); return new Promise((resolve) => { const stmt = ` - INSERT INTO favorites (id, title, image_url, thumbnail_url, tags, headers, provider) - VALUES (?, ?, ?, ?, ?, ?, ?) + INSERT INTO favorites (id, user_id, title, image_url, thumbnail_url, tags, headers, provider) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; db.run( stmt, [ fav.id, + fav.user_id, fav.title, fav.image_url, fav.thumbnail_url, @@ -172,18 +177,18 @@ export async function addFavorite(fav: Favorite): Promise { }); } -export async function removeFavorite(id: string): Promise { +export async function removeFavorite(id: string, userId: number): Promise { const db = getDatabase("favorites"); return new Promise((resolve) => { - const stmt = 'DELETE FROM favorites WHERE id = ?'; + const stmt = 'DELETE FROM favorites WHERE id = ? AND user_id = ?'; - db.run(stmt, [id], function (err: Error | null) { + db.run(stmt, [id, userId], function (err: Error | null) { if (err) { console.error('Error removing favorite:', err.message); resolve({ success: false, error: err.message }); } else { - // @ts-ignore - this.changes existe en el contexto de db.run + // @ts-ignore resolve({ success: this.changes > 0 }); } }); diff --git a/src/scripts/gallery/gallery.js b/src/scripts/gallery/gallery.js index 258fcf5..adbbc28 100644 --- a/src/scripts/gallery/gallery.js +++ b/src/scripts/gallery/gallery.js @@ -19,6 +19,13 @@ sentinel.style.marginBottom = '300px'; sentinel.style.display = 'none'; resultsContainer.parentNode.appendChild(sentinel); +function getAuthHeaders(extra = {}) { + const token = localStorage.getItem("token"); + return token + ? { ...extra, Authorization: `Bearer ${token}` } + : extra; +} + function initializeMasonry() { if (typeof Masonry === 'undefined') { setTimeout(initializeMasonry, 100); @@ -84,7 +91,9 @@ function getProxiedImageUrl(item) { async function loadFavorites() { try { - const res = await fetch('/api/gallery/favorites'); + const res = await fetch('/api/gallery/favorites', { + headers: getAuthHeaders() + }); if (!res.ok) return; const data = await res.json(); favorites.clear(); @@ -100,7 +109,10 @@ async function toggleFavorite(item) { try { if (isFav) { - await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`, { method: 'DELETE' }); + await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); favorites.delete(id); } else { const tagsArray = getTagsArray(item); @@ -109,7 +121,9 @@ async function toggleFavorite(item) { await fetch('/api/gallery/favorites', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: getAuthHeaders({ + 'Content-Type': 'application/json' + }), body: JSON.stringify({ id: item.id, title: item.title || tagsArray.join(', ') || 'Untitled', @@ -121,6 +135,7 @@ async function toggleFavorite(item) { }) }); + favorites.add(id); } @@ -238,7 +253,9 @@ async function searchGallery(isLoadMore = false) { let data; if (favoritesMode) { - const res = await fetch('/api/gallery/favorites'); + const res = await fetch('/api/gallery/favorites', { + headers: getAuthHeaders() + }); data = await res.json(); let favoritesResults = data.favorites || []; diff --git a/src/scripts/gallery/image.js b/src/scripts/gallery/image.js index ab6acc7..f0d041e 100644 --- a/src/scripts/gallery/image.js +++ b/src/scripts/gallery/image.js @@ -1,6 +1,13 @@ const itemMainContentContainer = document.getElementById('item-main-content'); let currentItem = null; +function getAuthHeaders(extra = {}) { + const token = localStorage.getItem("token"); + return token + ? { ...extra, Authorization: `Bearer ${token}` } + : extra; +} + function getProxiedItemUrl(url, headers = null) { if (!url || !headers) { return url; @@ -52,14 +59,19 @@ async function toggleFavorite() { try { if (wasFavorited) { - await fetch(`/api/gallery/favorites/${encodeURIComponent(currentItem.id)}`, { method: 'DELETE' }); + await fetch(`/api/gallery/favorites/${encodeURIComponent(currentItem.id)}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); } else { const serializedHeaders = currentItem.headers ? JSON.stringify(currentItem.headers) : ""; const tagsString = Array.isArray(currentItem.tags) ? currentItem.tags.join(',') : (currentItem.tags || ''); await fetch('/api/gallery/favorites', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: getAuthHeaders({ + 'Content-Type': 'application/json' + }), body: JSON.stringify({ id: currentItem.id, title: currentItem.title || 'Waifu', @@ -70,6 +82,7 @@ async function toggleFavorite() { headers: serializedHeaders }) }); + } btn.classList.toggle('favorited', !wasFavorited); @@ -216,7 +229,9 @@ function renderError(msg) { async function loadFromFavorites(id) { try { - const res = await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`); + const res = await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`, { + headers: getAuthHeaders() + }); if (!res.ok) throw new Error('Not found'); const { favorite: fav } = await res.json(); @@ -269,7 +284,9 @@ async function loadFromProvider(provider, id) { renderItem(item); document.getElementById('page-title').textContent = `WaifuBoard - ${item.title}`; - const favRes = await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`); + const favRes = await fetch(`/api/gallery/favorites/${encodeURIComponent(id)}`, { + headers: getAuthHeaders() + }); if (favRes.ok) { document.getElementById('fav-btn')?.classList.add('favorited'); const btn = document.getElementById('fav-btn'); diff --git a/src/shared/database.js b/src/shared/database.js index edab3d5..8bc28a0 100644 --- a/src/shared/database.js +++ b/src/shared/database.js @@ -36,7 +36,6 @@ async function ensureUserDataDB(dbPath) { created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); - -- Tabla 2: UserIntegration (✅ ACTUALIZADA) CREATE TABLE IF NOT EXISTS UserIntegration ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE, @@ -49,7 +48,6 @@ async function ensureUserDataDB(dbPath) { FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE ); - -- Tabla 3: ListEntry CREATE TABLE IF NOT EXISTS ListEntry ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, @@ -124,14 +122,16 @@ function ensureFavoritesDB(dbPath) { if (!exists) { const schema = ` CREATE TABLE IF NOT EXISTS favorites ( - id TEXT PRIMARY KEY, + id TEXT NOT NULL, + user_id INTEGER NOT NULL, title TEXT NOT NULL, image_url TEXT NOT NULL, thumbnail_url TEXT NOT NULL DEFAULT "", tags TEXT NOT NULL DEFAULT "", headers TEXT NOT NULL DEFAULT "", - provider TEXT NOT NULL DEFAULT "" - ); + provider TEXT NOT NULL DEFAULT "", + PRIMARY KEY (id, user_id) + ); `; db.exec(schema, (err) => { @@ -147,6 +147,7 @@ function ensureFavoritesDB(dbPath) { const hasHeaders = cols.some(c => c.name === "headers"); const hasProvider = cols.some(c => c.name === "provider"); + const hasUserId = cols.some(c => c.name === "user_id"); const queries = []; @@ -158,6 +159,10 @@ function ensureFavoritesDB(dbPath) { queries.push(`ALTER TABLE favorites ADD COLUMN provider TEXT NOT NULL DEFAULT ""`); } + if (!hasUserId) { + queries.push(`ALTER TABLE favorites ADD COLUMN user_id INTEGER NOT NULL DEFAULT 1`); + } + if (queries.length === 0) { return resolve(false); }