refactor: migrate from MongoDB to Prisma for data management, removing mongoose models and updating services to use Prisma client

This commit is contained in:
Julien Froidefond
2025-10-16 22:22:20 +02:00
parent 677e2ae884
commit 3cd58f63e6
21 changed files with 636 additions and 576 deletions

View File

@@ -1,5 +1,4 @@
import connectDB from "@/lib/mongodb";
import { UserModel } from "@/lib/models/user.model";
import prisma from "@/lib/prisma";
import bcrypt from "bcryptjs";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
@@ -15,15 +14,16 @@ export class AuthServerService {
private static readonly SALT_ROUNDS = 10;
static async registerUser(email: string, password: string): Promise<UserData> {
await connectDB();
//check if password is strong
if (!AuthServerService.isPasswordStrong(password)) {
throw new AppError(ERROR_CODES.AUTH.PASSWORD_NOT_STRONG);
}
// Check if user already exists
const existingUser = await UserModel.findOne({ email: email.toLowerCase() });
const existingUser = await prisma.user.findUnique({
where: { email: email.toLowerCase() },
});
if (existingUser) {
throw new AppError(ERROR_CODES.AUTH.EMAIL_EXISTS);
}
@@ -32,15 +32,17 @@ export class AuthServerService {
const hashedPassword = await bcrypt.hash(password, this.SALT_ROUNDS);
// Create new user
const user = await UserModel.create({
email: email.toLowerCase(),
password: hashedPassword,
roles: ["ROLE_USER"],
authenticated: true,
const user = await prisma.user.create({
data: {
email: email.toLowerCase(),
password: hashedPassword,
roles: ["ROLE_USER"],
authenticated: true,
},
});
const userData: UserData = {
id: user._id.toString(),
id: user.id,
email: user.email,
roles: user.roles,
authenticated: true,
@@ -67,9 +69,9 @@ export class AuthServerService {
}
static async loginUser(email: string, password: string): Promise<UserData> {
await connectDB();
const user = await UserModel.findOne({ email: email.toLowerCase() });
const user = await prisma.user.findUnique({
where: { email: email.toLowerCase() },
});
if (!user) {
throw new AppError(ERROR_CODES.AUTH.INVALID_CREDENTIALS);
@@ -82,7 +84,7 @@ export class AuthServerService {
}
const userData: UserData = {
id: user._id.toString(),
id: user.id,
email: user.email,
roles: user.roles,
authenticated: true,

View File

@@ -1,6 +1,4 @@
import connectDB from "@/lib/mongodb";
import { KomgaConfig as KomgaConfigModel } from "@/lib/models/config.model";
import { TTLConfig as TTLConfigModel } from "@/lib/models/ttl-config.model";
import prisma from "@/lib/prisma";
import { DebugService } from "./debug.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
@@ -19,28 +17,27 @@ export class ConfigDBService {
static async saveConfig(data: KomgaConfigData): Promise<KomgaConfig> {
try {
const user: User | null = await this.getCurrentUser();
await connectDB();
const authHeader: string = Buffer.from(`${data.username}:${data.password}`).toString(
"base64"
);
const config: KomgaConfig | null = await KomgaConfigModel.findOneAndUpdate(
{ userId: user.id },
{
const config = await prisma.komgaConfig.upsert({
where: { userId: user.id },
update: {
url: data.url,
username: data.username,
authHeader,
},
create: {
userId: user.id,
url: data.url,
username: data.username,
// password: data.password,
authHeader,
},
{ upsert: true, new: true }
);
if (!config) {
throw new AppError(ERROR_CODES.CONFIG.SAVE_ERROR);
}
});
return config;
return config as KomgaConfig;
} catch (error) {
if (error instanceof AppError) {
throw error;
@@ -52,11 +49,12 @@ export class ConfigDBService {
static async getConfig(): Promise<KomgaConfig | null> {
try {
const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getConfig", async () => {
const config: KomgaConfig | null = await KomgaConfigModel.findOne({ userId: user.id });
return config;
const config = await prisma.komgaConfig.findUnique({
where: { userId: user.id },
});
return config as KomgaConfig | null;
});
} catch (error) {
if (error instanceof AppError) {
@@ -69,11 +67,12 @@ export class ConfigDBService {
static async getTTLConfig(): Promise<TTLConfig | null> {
try {
const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getTTLConfig", async () => {
const config: TTLConfig | null = await TTLConfigModel.findOne({ userId: user.id });
return config;
const config = await prisma.tTLConfig.findUnique({
where: { userId: user.id },
});
return config as TTLConfig | null;
});
} catch (error) {
if (error instanceof AppError) {
@@ -86,23 +85,30 @@ export class ConfigDBService {
static async saveTTLConfig(data: TTLConfigData): Promise<TTLConfig> {
try {
const user: User | null = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("saveTTLConfig", async () => {
const config: TTLConfig | null = await TTLConfigModel.findOneAndUpdate(
{ userId: user.id },
{
userId: user.id,
...data,
const config = await prisma.tTLConfig.upsert({
where: { userId: user.id },
update: {
defaultTTL: data.defaultTTL,
homeTTL: data.homeTTL,
librariesTTL: data.librariesTTL,
seriesTTL: data.seriesTTL,
booksTTL: data.booksTTL,
imagesTTL: data.imagesTTL,
},
{ upsert: true, new: true }
);
create: {
userId: user.id,
defaultTTL: data.defaultTTL,
homeTTL: data.homeTTL,
librariesTTL: data.librariesTTL,
seriesTTL: data.seriesTTL,
booksTTL: data.booksTTL,
imagesTTL: data.imagesTTL,
},
});
if (!config) {
throw new AppError(ERROR_CODES.CONFIG.TTL_SAVE_ERROR);
}
return config;
return config as TTLConfig;
});
} catch (error) {
if (error instanceof AppError) {

View File

@@ -1,5 +1,4 @@
import connectDB from "@/lib/mongodb";
import { FavoriteModel } from "@/lib/models/favorite.model";
import prisma from "@/lib/prisma";
import { DebugService } from "./debug.service";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
@@ -30,12 +29,13 @@ export class FavoriteService {
static async isFavorite(seriesId: string): Promise<boolean> {
try {
const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("isFavorite", async () => {
const favorite = await FavoriteModel.findOne({
userId: user.id,
seriesId: seriesId,
const favorite = await prisma.favorite.findFirst({
where: {
userId: user.id,
seriesId: seriesId,
},
});
return !!favorite;
});
@@ -51,14 +51,21 @@ export class FavoriteService {
static async addToFavorites(seriesId: string): Promise<void> {
try {
const user = await this.getCurrentUser();
await connectDB();
await DebugService.measureMongoOperation("addToFavorites", async () => {
await FavoriteModel.findOneAndUpdate(
{ userId: user.id, seriesId },
{ userId: user.id, seriesId },
{ upsert: true }
);
await prisma.favorite.upsert({
where: {
userId_seriesId: {
userId: user.id,
seriesId,
},
},
update: {},
create: {
userId: user.id,
seriesId,
},
});
});
this.dispatchFavoritesChanged();
@@ -73,12 +80,13 @@ export class FavoriteService {
static async removeFromFavorites(seriesId: string): Promise<void> {
try {
const user = await this.getCurrentUser();
await connectDB();
await DebugService.measureMongoOperation("removeFromFavorites", async () => {
await FavoriteModel.findOneAndDelete({
userId: user.id,
seriesId,
await prisma.favorite.deleteMany({
where: {
userId: user.id,
seriesId,
},
});
});
@@ -93,35 +101,48 @@ export class FavoriteService {
*/
static async getAllFavoriteIds(): Promise<string[]> {
const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("getAllFavoriteIds", async () => {
const favorites = await FavoriteModel.find({ userId: user.id });
const favorites = await prisma.favorite.findMany({
where: { userId: user.id },
select: { seriesId: true },
});
return favorites.map((favorite) => favorite.seriesId);
});
}
static async addFavorite(seriesId: string) {
const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("addFavorite", async () => {
const favorite = await FavoriteModel.findOneAndUpdate(
{ userId: user.id, seriesId },
{ userId: user.id, seriesId },
{ upsert: true, new: true }
);
const favorite = await prisma.favorite.upsert({
where: {
userId_seriesId: {
userId: user.id,
seriesId,
},
},
update: {},
create: {
userId: user.id,
seriesId,
},
});
return favorite;
});
}
static async removeFavorite(seriesId: string): Promise<boolean> {
const user = await this.getCurrentUser();
await connectDB();
return DebugService.measureMongoOperation("removeFavorite", async () => {
const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId });
return result.deletedCount > 0;
const result = await prisma.favorite.deleteMany({
where: {
userId: user.id,
seriesId,
},
});
return result.count > 0;
});
}
}

View File

@@ -1,11 +1,10 @@
import { PreferencesModel } from "@/lib/models/preferences.model";
import prisma from "@/lib/prisma";
import { getCurrentUser } from "../auth-utils";
import { ERROR_CODES } from "../../constants/errorCodes";
import { AppError } from "../../utils/errors";
import type { UserPreferences } from "@/types/preferences";
import { defaultPreferences } from "@/types/preferences";
import type { User } from "@/types/komga";
import connectDB from "@/lib/mongodb";
export class PreferencesService {
static async getCurrentUser(): Promise<User> {
@@ -19,15 +18,20 @@ export class PreferencesService {
static async getPreferences(): Promise<UserPreferences> {
try {
const user = await this.getCurrentUser();
await connectDB();
const preferences = await PreferencesModel.findOne({ userId: user.id });
const preferences = await prisma.preferences.findUnique({
where: { userId: user.id },
});
if (!preferences) {
return defaultPreferences;
return { ...defaultPreferences };
}
return {
...defaultPreferences,
...preferences.toObject(),
_id: undefined, //plain object KO on server components hydration
showThumbnails: preferences.showThumbnails,
cacheMode: preferences.cacheMode as "memory" | "file",
showOnlyUnread: preferences.showOnlyUnread,
debug: preferences.debug,
displayMode: preferences.displayMode as UserPreferences["displayMode"],
};
} catch (error) {
if (error instanceof AppError) {
@@ -40,18 +44,34 @@ export class PreferencesService {
static async updatePreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
try {
const user = await this.getCurrentUser();
await connectDB();
const updatedPreferences = await PreferencesModel.findOneAndUpdate(
{ userId: user.id },
{ $set: preferences },
{ new: true, upsert: true }
);
const updateData: Record<string, any> = {};
if (preferences.showThumbnails !== undefined) updateData.showThumbnails = preferences.showThumbnails;
if (preferences.cacheMode !== undefined) updateData.cacheMode = preferences.cacheMode;
if (preferences.showOnlyUnread !== undefined) updateData.showOnlyUnread = preferences.showOnlyUnread;
if (preferences.debug !== undefined) updateData.debug = preferences.debug;
if (preferences.displayMode !== undefined) updateData.displayMode = preferences.displayMode;
const result = {
...defaultPreferences,
...updatedPreferences.toObject(),
const updatedPreferences = await prisma.preferences.upsert({
where: { userId: user.id },
update: updateData,
create: {
userId: user.id,
showThumbnails: preferences.showThumbnails ?? defaultPreferences.showThumbnails,
cacheMode: preferences.cacheMode ?? defaultPreferences.cacheMode,
showOnlyUnread: preferences.showOnlyUnread ?? defaultPreferences.showOnlyUnread,
debug: preferences.debug ?? defaultPreferences.debug,
displayMode: preferences.displayMode ?? defaultPreferences.displayMode,
},
});
return {
showThumbnails: updatedPreferences.showThumbnails,
cacheMode: updatedPreferences.cacheMode as "memory" | "file",
showOnlyUnread: updatedPreferences.showOnlyUnread,
debug: updatedPreferences.debug,
displayMode: updatedPreferences.displayMode as UserPreferences["displayMode"],
};
return result;
} catch (error) {
if (error instanceof AppError) {
throw error;