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

@@ -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[]) => {
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<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,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);
}