refacto(db): favorites on db
This commit is contained in:
2
.env
2
.env
@@ -1,7 +1,7 @@
|
|||||||
# MongoDB
|
# MongoDB
|
||||||
MONGO_USER=admin
|
MONGO_USER=admin
|
||||||
MONGO_PASSWORD=password
|
MONGO_PASSWORD=password
|
||||||
MONGODB_URI=mongodb://admin:password@localhost:27017/stripstream?authSource=admin
|
MONGODB_URI=mongodb://admin:password@mongodb.paniels.orb.local:27017/stripstream?authSource=admin
|
||||||
|
|
||||||
# Komga
|
# Komga
|
||||||
NEXT_PUBLIC_API_URL=https://cloud.julienfroidefond.com
|
NEXT_PUBLIC_API_URL=https://cloud.julienfroidefond.com
|
||||||
37
src/app/api/komga/favorites/route.ts
Normal file
37
src/app/api/komga/favorites/route.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { FavoriteService } from "@/lib/services/favorite.service";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const favoriteIds = await FavoriteService.getAllFavoriteIds();
|
||||||
|
return NextResponse.json(favoriteIds);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des favoris:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Erreur lors de la récupération des favoris" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const { seriesId } = await request.json();
|
||||||
|
await FavoriteService.addToFavorites(seriesId);
|
||||||
|
return NextResponse.json({ message: "Favori ajouté avec succès" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de l'ajout du favori:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur lors de l'ajout du favori" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(request: Request) {
|
||||||
|
try {
|
||||||
|
const { seriesId } = await request.json();
|
||||||
|
await FavoriteService.removeFromFavorites(seriesId);
|
||||||
|
return NextResponse.json({ message: "Favori supprimé avec succès" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la suppression du favori:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur lors de la suppression du favori" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,10 +11,10 @@ interface HeroSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HeroSection({ series }: HeroSectionProps) {
|
export function HeroSection({ series }: HeroSectionProps) {
|
||||||
console.log("HeroSection - Séries reçues:", {
|
// console.log("HeroSection - Séries reçues:", {
|
||||||
count: series?.length || 0,
|
// count: series?.length || 0,
|
||||||
firstSeries: series?.[0],
|
// firstSeries: series?.[0],
|
||||||
});
|
// });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-[500px] -mx-4 sm:-mx-8 lg:-mx-14 overflow-hidden">
|
<div className="relative h-[500px] -mx-4 sm:-mx-8 lg:-mx-14 overflow-hidden">
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ export function HomeContent({ data }: HomeContentProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Vérification des données pour le debug
|
// Vérification des données pour le debug
|
||||||
console.log("HomeContent - Données reçues:", {
|
// console.log("HomeContent - Données reçues:", {
|
||||||
ongoingCount: data.ongoing?.length || 0,
|
// ongoingCount: data.ongoing?.length || 0,
|
||||||
recentlyReadCount: data.recentlyRead?.length || 0,
|
// recentlyReadCount: data.recentlyRead?.length || 0,
|
||||||
onDeckCount: data.onDeck?.length || 0,
|
// onDeckCount: data.onDeck?.length || 0,
|
||||||
});
|
// });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="container mx-auto px-4 py-8 space-y-12">
|
<main className="container mx-auto px-4 py-8 space-y-12">
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
|
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
@@ -6,7 +8,6 @@ import { authService } from "@/lib/services/auth.service";
|
|||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import { KomgaLibrary, KomgaSeries } from "@/types/komga";
|
import { KomgaLibrary, KomgaSeries } from "@/types/komga";
|
||||||
import { storageService } from "@/lib/services/storage.service";
|
import { storageService } from "@/lib/services/storage.service";
|
||||||
import { FavoriteService } from "@/lib/services/favorite.service";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -43,13 +44,20 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
const fetchFavorites = useCallback(async () => {
|
const fetchFavorites = useCallback(async () => {
|
||||||
setIsLoadingFavorites(true);
|
setIsLoadingFavorites(true);
|
||||||
try {
|
try {
|
||||||
const favoriteIds = FavoriteService.getAllFavoriteIds();
|
// Récupérer les IDs des favoris depuis l'API
|
||||||
|
const favoritesResponse = await fetch("/api/komga/favorites");
|
||||||
|
if (!favoritesResponse.ok) {
|
||||||
|
throw new Error("Erreur lors de la récupération des favoris");
|
||||||
|
}
|
||||||
|
const favoriteIds = await favoritesResponse.json();
|
||||||
|
|
||||||
if (favoriteIds.length === 0) {
|
if (favoriteIds.length === 0) {
|
||||||
setFavorites([]);
|
setFavorites([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = favoriteIds.map(async (id) => {
|
// Récupérer les détails des séries pour chaque ID
|
||||||
|
const promises = favoriteIds.map(async (id: string) => {
|
||||||
const response = await fetch(`/api/komga/series/${id}`);
|
const response = await fetch(`/api/komga/series/${id}`);
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
return response.json();
|
return response.json();
|
||||||
@@ -69,7 +77,7 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchLibraries();
|
fetchLibraries();
|
||||||
fetchFavorites();
|
fetchFavorites();
|
||||||
}, []); // Suppression de la dépendance pathname
|
}, [fetchLibraries, fetchFavorites]);
|
||||||
|
|
||||||
// Mettre à jour les favoris quand ils changent
|
// Mettre à jour les favoris quand ils changent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,18 +85,10 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
fetchFavorites();
|
fetchFavorites();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Écouter les changements de favoris dans la même fenêtre
|
|
||||||
window.addEventListener("favoritesChanged", handleFavoritesChange);
|
window.addEventListener("favoritesChanged", handleFavoritesChange);
|
||||||
// Écouter les changements de favoris dans d'autres fenêtres
|
|
||||||
window.addEventListener("storage", (e) => {
|
|
||||||
if (e.key === "stripstream_favorites") {
|
|
||||||
fetchFavorites();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("favoritesChanged", handleFavoritesChange);
|
window.removeEventListener("favoritesChanged", handleFavoritesChange);
|
||||||
window.removeEventListener("storage", handleFavoritesChange);
|
|
||||||
};
|
};
|
||||||
}, [fetchFavorites]);
|
}, [fetchFavorites]);
|
||||||
|
|
||||||
|
|||||||
26
src/components/layout/SidebarWrapper.tsx
Normal file
26
src/components/layout/SidebarWrapper.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { FavoriteService } from "@/lib/services/favorite.service";
|
||||||
|
import { LibraryService } from "@/lib/services/library.service";
|
||||||
|
import { ClientSidebar } from "./ClientSidebar";
|
||||||
|
|
||||||
|
export async function SidebarWrapper() {
|
||||||
|
// Récupérer les favoris depuis le serveur
|
||||||
|
const favoriteIds = await FavoriteService.getAllFavoriteIds();
|
||||||
|
|
||||||
|
// Récupérer les détails des séries favorites
|
||||||
|
const favoritesPromises = favoriteIds.map(async (id) => {
|
||||||
|
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/series/${id}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) return null;
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Récupérer les bibliothèques
|
||||||
|
const libraries = await LibraryService.getLibraries();
|
||||||
|
|
||||||
|
const favorites = (await Promise.all(favoritesPromises)).filter(Boolean);
|
||||||
|
|
||||||
|
return { favorites, libraries };
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ImageOff, Book, BookOpen, BookMarked } from "lucide-react";
|
import { ImageOff, Book, BookOpen, BookMarked, Star, StarOff } from "lucide-react";
|
||||||
import { KomgaSeries } from "@/types/komga";
|
import { KomgaSeries } from "@/types/komga";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { FavoriteService } from "@/lib/services/favorite.service";
|
|
||||||
import { Star, StarOff } from "lucide-react";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -56,13 +54,27 @@ export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => {
|
|||||||
const [readingStatus, setReadingStatus] = useState<ReadingStatusInfo>(
|
const [readingStatus, setReadingStatus] = useState<ReadingStatusInfo>(
|
||||||
getReadingStatusInfo(series)
|
getReadingStatusInfo(series)
|
||||||
);
|
);
|
||||||
const [isFavorite, setIsFavorite] = useState(FavoriteService.isFavorite(series.id));
|
const [isFavorite, setIsFavorite] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
|
// Vérifier si la série est dans les favoris au chargement
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const checkFavorite = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/komga/favorites");
|
||||||
|
if (response.ok) {
|
||||||
|
const favoriteIds = await response.json();
|
||||||
|
setIsFavorite(favoriteIds.includes(series.id));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la vérification des favoris:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkFavorite();
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, [series.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReadingStatus(getReadingStatusInfo(series));
|
setReadingStatus(getReadingStatusInfo(series));
|
||||||
@@ -82,18 +94,29 @@ export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => {
|
|||||||
}
|
}
|
||||||
}, [series.metadata.language]);
|
}, [series.metadata.language]);
|
||||||
|
|
||||||
const handleToggleFavorite = () => {
|
const handleToggleFavorite = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (isFavorite) {
|
const response = await fetch("/api/komga/favorites", {
|
||||||
FavoriteService.removeFromFavorites(series.id);
|
method: isFavorite ? "DELETE" : "POST",
|
||||||
} else {
|
headers: {
|
||||||
FavoriteService.addToFavorites(series.id);
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ seriesId: series.id }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Erreur lors de la modification des favoris");
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsFavorite(!isFavorite);
|
setIsFavorite(!isFavorite);
|
||||||
if (onSeriesUpdate) {
|
if (onSeriesUpdate) {
|
||||||
onSeriesUpdate({ ...series, favorite: !isFavorite });
|
onSeriesUpdate({ ...series, favorite: !isFavorite });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch l'événement pour notifier les autres composants
|
||||||
|
window.dispatchEvent(new Event("favoritesChanged"));
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: isFavorite ? "Retiré des favoris" : "Ajouté aux favoris",
|
title: isFavorite ? "Retiré des favoris" : "Ajouté aux favoris",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
|
|||||||
23
src/lib/models/favorite.model.ts
Normal file
23
src/lib/models/favorite.model.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
|
const favoriteSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
seriesId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Index composé pour s'assurer qu'un utilisateur ne peut pas avoir deux fois le même favori
|
||||||
|
favoriteSchema.index({ userId: 1, seriesId: 1 }, { unique: true });
|
||||||
|
|
||||||
|
export const FavoriteModel = mongoose.models.Favorite || mongoose.model("Favorite", favoriteSchema);
|
||||||
@@ -1,40 +1,111 @@
|
|||||||
import { storageService } from "./storage.service";
|
import { cookies } from "next/headers";
|
||||||
|
import connectDB from "@/lib/mongodb";
|
||||||
|
import { FavoriteModel } from "@/lib/models/favorite.model";
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class FavoriteService {
|
export class FavoriteService {
|
||||||
private static readonly FAVORITES_CHANGE_EVENT = "favoritesChanged";
|
private static readonly FAVORITES_CHANGE_EVENT = "favoritesChanged";
|
||||||
|
|
||||||
private static dispatchFavoritesChanged() {
|
private static dispatchFavoritesChanged() {
|
||||||
// Dispatch l'événement pour notifier les changements
|
// Dispatch l'événement pour notifier les changements
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
window.dispatchEvent(new Event(FavoriteService.FAVORITES_CHANGE_EVENT));
|
window.dispatchEvent(new Event(FavoriteService.FAVORITES_CHANGE_EVENT));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getCurrentUser(): Promise<User> {
|
||||||
|
const userCookie = cookies().get("stripUser");
|
||||||
|
|
||||||
|
if (!userCookie) {
|
||||||
|
throw new Error("Utilisateur non authentifié");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(atob(userCookie.value));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error);
|
||||||
|
throw new Error("Utilisateur non authentifié");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifie si une série est dans les favoris
|
* Vérifie si une série est dans les favoris
|
||||||
*/
|
*/
|
||||||
static isFavorite(seriesId: string): boolean {
|
static async isFavorite(seriesId: string): Promise<boolean> {
|
||||||
return storageService.isFavorite(seriesId);
|
try {
|
||||||
|
const user = await this.getCurrentUser();
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
const favorite = await FavoriteModel.findOne({
|
||||||
|
userId: user.id,
|
||||||
|
seriesId: seriesId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!favorite;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la vérification du favori:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute une série aux favoris
|
* Ajoute une série aux favoris
|
||||||
*/
|
*/
|
||||||
static addToFavorites(seriesId: string): void {
|
static async addToFavorites(seriesId: string): Promise<void> {
|
||||||
storageService.addFavorite(seriesId);
|
try {
|
||||||
|
const user = await this.getCurrentUser();
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
await FavoriteModel.findOneAndUpdate(
|
||||||
|
{ userId: user.id, seriesId },
|
||||||
|
{ userId: user.id, seriesId },
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
|
||||||
this.dispatchFavoritesChanged();
|
this.dispatchFavoritesChanged();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de l'ajout aux favoris:", error);
|
||||||
|
throw new Error("Erreur lors de l'ajout aux favoris");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retire une série des favoris
|
* Retire une série des favoris
|
||||||
*/
|
*/
|
||||||
static removeFromFavorites(seriesId: string): void {
|
static async removeFromFavorites(seriesId: string): Promise<void> {
|
||||||
storageService.removeFavorite(seriesId);
|
try {
|
||||||
|
const user = await this.getCurrentUser();
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
await FavoriteModel.findOneAndDelete({
|
||||||
|
userId: user.id,
|
||||||
|
seriesId,
|
||||||
|
});
|
||||||
|
|
||||||
this.dispatchFavoritesChanged();
|
this.dispatchFavoritesChanged();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la suppression des favoris:", error);
|
||||||
|
throw new Error("Erreur lors de la suppression des favoris");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère tous les IDs des séries favorites
|
* Récupère tous les IDs des séries favorites
|
||||||
*/
|
*/
|
||||||
static getAllFavoriteIds(): string[] {
|
static async getAllFavoriteIds(): Promise<string[]> {
|
||||||
return storageService.getFavorites();
|
try {
|
||||||
|
const user = await this.getCurrentUser();
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
const favorites = await FavoriteModel.find({ userId: user.id });
|
||||||
|
return favorites.map((favorite) => favorite.seriesId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des favoris:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user