gallery now supports multiple users
This commit is contained in:
@@ -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" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
@@ -102,28 +102,32 @@ export async function searchInExtension(providerName: string, query: string, pag
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFavorites(): Promise<Favorite[]> {
|
||||
export async function getFavorites(userId: number): Promise<Favorite[]> {
|
||||
const db = getDatabase("favorites");
|
||||
|
||||
return new Promise((resolve) => {
|
||||
db.all('SELECT * FROM favorites', [], (err: Error | null, rows: Favorite[]) => {
|
||||
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<Favorite | null> {
|
||||
export async function getFavoriteById(id: string, userId: number): Promise<Favorite | null> {
|
||||
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<Favorite | null> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function addFavorite(fav: Favorite): Promise<FavoriteResult> {
|
||||
export async function addFavorite(fav: Favorite & { user_id: number }): Promise<FavoriteResult> {
|
||||
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<FavoriteResult> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeFavorite(id: string): Promise<FavoriteResult> {
|
||||
export async function removeFavorite(id: string, userId: number): Promise<FavoriteResult> {
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,13 +122,15 @@ 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)
|
||||
);
|
||||
`;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user