Merge branch 'main' into feat/debugmode

This commit is contained in:
Julien Froidefond
2025-02-23 21:30:34 +01:00
20 changed files with 548 additions and 223 deletions

View File

@@ -1,6 +1,7 @@
import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
import { UserModel } from "@/lib/models/user.model";
import bcrypt from "bcrypt";
interface UserData {
id: string;
@@ -10,6 +11,8 @@ interface UserData {
}
export class AuthServerService {
private static readonly SALT_ROUNDS = 10;
static async createUser(email: string, password: string): Promise<UserData> {
await connectDB();
@@ -24,10 +27,13 @@ export class AuthServerService {
throw new Error("EMAIL_EXISTS");
}
// Hash password
const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS);
// Create new user
const user = await UserModel.create({
email: email.toLowerCase(),
password,
password: hashedPassword,
roles: ["ROLE_USER"],
authenticated: true,
});
@@ -41,6 +47,7 @@ export class AuthServerService {
return userData;
}
static isPasswordStrong(password: string): boolean {
//check if password is strong
if (password.length < 8) {
@@ -52,9 +59,9 @@ export class AuthServerService {
if (!/[0-9]/.test(password)) {
return false;
}
if (!/[!@#$%^&*]/.test(password)) {
return false;
}
// if (!/[!@#$%^&*]/.test(password)) {
// return false;
// }
return true;
}
@@ -95,7 +102,8 @@ export class AuthServerService {
throw new Error("INVALID_CREDENTIALS");
}
if (user.password !== password) {
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw new Error("INVALID_CREDENTIALS");
}

View File

@@ -1,5 +1,5 @@
import { AuthConfig } from "@/types/auth";
import { serverCacheService } from "./server-cache.service";
import { getServerCacheService } from "./server-cache.service";
import { ConfigDBService } from "./config-db.service";
import { DebugService } from "./debug.service";
@@ -52,10 +52,11 @@ export abstract class BaseApiService {
fetcher: () => Promise<T>,
type: CacheType = "DEFAULT"
): Promise<T> {
const cacheService = await getServerCacheService();
const startTime = performance.now();
try {
const result = await serverCacheService.getOrSet(key, fetcher, type);
const result = await cacheService.getOrSet(key, fetcher, type);
const endTime = performance.now();
// Log la requête avec l'indication du cache

View File

@@ -1,4 +1,3 @@
import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
import { KomgaConfig } from "@/lib/models/config.model";
import { TTLConfig } from "@/lib/models/ttl-config.model";
@@ -34,6 +33,24 @@ export class ConfigDBService {
return user;
}
static async saveConfig(data: KomgaConfigData) {
const user = this.getCurrentUser();
await connectDB();
const config = await KomgaConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
url: data.url,
username: data.username,
password: data.password,
},
{ upsert: true, new: true }
);
return config;
}
static async getConfig() {
const user = this.getCurrentUser();
await connectDB();
@@ -44,25 +61,6 @@ export class ConfigDBService {
});
}
static async saveConfig(data: KomgaConfigData) {
const user = this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("saveConfig", async () => {
const config = await KomgaConfig.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
url: data.url,
username: data.username,
password: data.password,
},
{ upsert: true, new: true }
);
return config;
});
}
static async getTTLConfig() {
const user = this.getCurrentUser();
await connectDB();

View File

@@ -1,4 +1,3 @@
import { cookies } from "next/headers";
import connectDB from "@/lib/mongodb";
import { FavoriteModel } from "@/lib/models/favorite.model";
import { DebugService } from "./debug.service";

View File

@@ -1,19 +1,20 @@
import { BaseApiService } from "./base-api.service";
import { KomgaBook, KomgaSeries } from "@/types/komga";
import { LibraryResponse } from "@/types/library";
import { serverCacheService } from "./server-cache.service";
import { getServerCacheService } from "./server-cache.service";
interface HomeData {
ongoing: KomgaSeries[];
recentlyRead: KomgaBook[];
onDeck: KomgaBook[];
latestSeries: KomgaSeries[];
}
export class HomeService extends BaseApiService {
static async getHomeData(): Promise<HomeData> {
try {
// Appels API parallèles avec cache individuel
const [ongoing, recentlyRead, onDeck] = await Promise.all([
const [ongoing, recentlyRead, onDeck, latestSeries] = await Promise.all([
this.fetchWithCache<LibraryResponse<KomgaSeries>>(
"home-ongoing",
async () =>
@@ -55,21 +56,36 @@ export class HomeService extends BaseApiService {
}),
"HOME"
),
this.fetchWithCache<LibraryResponse<KomgaSeries>>(
"home-latest-series",
async () =>
this.fetchFromApi<LibraryResponse<KomgaSeries>>({
path: "series/latest",
params: {
page: "0",
size: "10",
media_status: "READY",
},
}),
"HOME"
),
]);
return {
ongoing: ongoing.content || [],
recentlyRead: recentlyRead.content || [],
onDeck: onDeck.content || [],
latestSeries: latestSeries.content || [],
};
} catch (error) {
return this.handleError(error, "Impossible de récupérer les données de la page d'accueil");
}
}
static async clearHomeCache() {
serverCacheService.delete("home-ongoing");
serverCacheService.delete("home-recently-read");
serverCacheService.delete("home-on-deck");
static async invalidateHomeCache(): Promise<void> {
const cacheService = await getServerCacheService();
cacheService.delete("home-ongoing");
cacheService.delete("home-recently-read");
cacheService.delete("home-on-deck");
}
}

View File

@@ -1,7 +1,7 @@
import { BaseApiService } from "./base-api.service";
import { Library, LibraryResponse } from "@/types/library";
import { Series } from "@/types/series";
import { serverCacheService } from "./server-cache.service";
import { getServerCacheService } from "./server-cache.service";
export class LibraryService extends BaseApiService {
static async getLibraries(): Promise<Library[]> {
@@ -134,7 +134,8 @@ export class LibraryService extends BaseApiService {
}
}
static async clearLibrarySeriesCache(libraryId: string) {
serverCacheService.delete(`library-${libraryId}-all-series`);
static async invalidateLibrarySeriesCache(libraryId: string): Promise<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`library-${libraryId}-all-series`);
}
}

View File

@@ -1,4 +1,3 @@
import { cookies } from "next/headers";
import { PreferencesModel } from "@/lib/models/preferences.model";
import { AuthServerService } from "./auth-server.service";

View File

@@ -4,7 +4,7 @@ import { KomgaBook, KomgaSeries } from "@/types/komga";
import { BookService } from "./book.service";
import { ImageService } from "./image.service";
import { PreferencesService } from "./preferences.service";
import { serverCacheService } from "./server-cache.service";
import { getServerCacheService } from "./server-cache.service";
export class SeriesService extends BaseApiService {
static async getSeries(seriesId: string): Promise<KomgaSeries> {
@@ -19,8 +19,9 @@ export class SeriesService extends BaseApiService {
}
}
static async clearSeriesCache(seriesId: string) {
serverCacheService.delete(`series-${seriesId}`);
static async invalidateSeriesCache(seriesId: string): Promise<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`series-${seriesId}`);
}
static async getAllSeriesBooks(seriesId: string): Promise<KomgaBook[]> {
@@ -125,8 +126,9 @@ export class SeriesService extends BaseApiService {
}
}
static async clearSeriesBooksCache(seriesId: string) {
serverCacheService.delete(`series-${seriesId}-all-books`);
static async invalidateSeriesBooksCache(seriesId: string): Promise<void> {
const cacheService = await getServerCacheService();
cacheService.delete(`series-${seriesId}-all-books`);
}
static async getFirstBook(seriesId: string): Promise<string> {

View File

@@ -1,5 +1,6 @@
import fs from "fs";
import path from "path";
import { PreferencesService } from "./preferences.service";
type CacheMode = "file" | "memory";
@@ -37,6 +38,17 @@ class ServerCacheService {
this.cacheDir = path.join(process.cwd(), ".cache");
this.ensureCacheDirectory();
this.cleanExpiredCache();
this.initializeCacheMode();
}
private async initializeCacheMode(): Promise<void> {
try {
const preferences = await PreferencesService.getPreferences();
this.setCacheMode(preferences.cacheMode);
} catch (error) {
console.error("Error initializing cache mode from preferences:", error);
// Keep default memory mode if preferences can't be loaded
}
}
private ensureCacheDirectory(): void {
@@ -117,9 +129,10 @@ class ServerCacheService {
cleanDirectory(this.cacheDir);
}
public static getInstance(): ServerCacheService {
public static async getInstance(): Promise<ServerCacheService> {
if (!ServerCacheService.instance) {
ServerCacheService.instance = new ServerCacheService();
await ServerCacheService.instance.initializeCacheMode();
}
return ServerCacheService.instance;
}
@@ -376,4 +389,15 @@ class ServerCacheService {
}
}
export const serverCacheService = ServerCacheService.getInstance();
// Créer une instance initialisée du service
let initializedInstance: Promise<ServerCacheService>;
export const getServerCacheService = async (): Promise<ServerCacheService> => {
if (!initializedInstance) {
initializedInstance = ServerCacheService.getInstance();
}
return initializedInstance;
};
// Exporter aussi la classe pour les tests
export { ServerCacheService };