gallery now supports multiple users

This commit is contained in:
2025-12-06 02:07:32 +01:00
parent 2df7625657
commit e7388bbb25
6 changed files with 97 additions and 51 deletions

View File

@@ -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" });
}
});

View File

@@ -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" });
}
}

View File

@@ -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 });
}
});

View File

@@ -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 || [];

View File

@@ -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');

View File

@@ -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);
}