feat: refactor inventory and recipe services for improved error handling and code reuse; add systematic backend review plan
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-04 20:44:43 +02:00
parent 6dfd4c372d
commit a645d6a364
6 changed files with 212 additions and 194 deletions
+37 -49
View File
@@ -31,6 +31,31 @@ export class RecipesService {
constructor(private readonly prisma: PrismaService) {}
private throwRecipeNotFound(id: number): never {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
private async findRecipeByIdOrThrow(id: number) {
const recipe = await this.prisma.recipe.findUnique({ where: { id } });
if (!recipe) {
this.throwRecipeNotFound(id);
}
return recipe;
}
private assertRecipeEditableByUser(recipe: { ownerId: number | null }, userId: number, id: number) {
// Legacy behavior: ownerless recipes are editable to preserve existing semantics.
if (recipe.ownerId !== null && recipe.ownerId !== userId) {
this.throwRecipeNotFound(id);
}
}
private assertRecipeOwnedByUser(recipe: { ownerId: number | null }, userId: number, id: number) {
if (recipe.ownerId !== userId) {
this.throwRecipeNotFound(id);
}
}
async getInventoryPreview(id: number, userId: number) {
const recipe = await this.prisma.recipe.findFirst({
where: {
@@ -218,19 +243,8 @@ export class RecipesService {
}
async update(id: number, updateRecipeDto: CreateRecipeDto, userId: number) {
// Verifiera att receptet finns och att användaren äger det
const existingRecipe = await this.prisma.recipe.findUnique({
where: { id },
});
if (!existingRecipe) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
// Tillåt uppdatering om användaren är ägare ELLER om receptet är publikt utan ägare
if (existingRecipe.ownerId !== null && existingRecipe.ownerId !== userId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const existingRecipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeEditableByUser(existingRecipe, userId, id);
// Ta bort gamla ingredienser
await this.prisma.recipeIngredient.deleteMany({
@@ -269,17 +283,8 @@ export class RecipesService {
}
async remove(id: number, userId: number) {
const existingRecipe = await this.prisma.recipe.findUnique({
where: { id },
});
if (!existingRecipe) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
if (existingRecipe.ownerId !== null && existingRecipe.ownerId !== userId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const existingRecipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeEditableByUser(existingRecipe, userId, id);
await this.prisma.recipeIngredient.deleteMany({ where: { recipeId: id } });
await this.prisma.recipe.delete({ where: { id } });
@@ -295,13 +300,8 @@ export class RecipesService {
}
async updateImage(id: number, sourceUrl: string, userId: number) {
const existingRecipe = await this.prisma.recipe.findUnique({ where: { id } });
if (!existingRecipe) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
if (existingRecipe.ownerId !== userId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const existingRecipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeOwnedByUser(existingRecipe, userId, id);
const imageUrl = await downloadAndOptimizeImage(sourceUrl, IMAGE_DEST_DIR);
@@ -317,10 +317,8 @@ export class RecipesService {
}
async setVisibility(id: number, userId: number, isPublic: boolean) {
const existingRecipe = await this.prisma.recipe.findUnique({ where: { id } });
if (!existingRecipe || existingRecipe.ownerId !== userId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const existingRecipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeOwnedByUser(existingRecipe, userId, id);
if (isPublic) {
const owner = await this.prisma.user.findUnique({
@@ -344,13 +342,8 @@ export class RecipesService {
}
async shareWithUser(id: number, ownerId: number, username: string) {
const recipe = await this.prisma.recipe.findUnique({
where: { id },
select: { id: true, ownerId: true },
});
if (!recipe || recipe.ownerId !== ownerId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const recipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeOwnedByUser(recipe, ownerId, id);
const owner = await this.prisma.user.findUnique({
where: { id: ownerId },
@@ -381,13 +374,8 @@ export class RecipesService {
}
async unshareWithUser(id: number, ownerId: number, username: string) {
const recipe = await this.prisma.recipe.findUnique({
where: { id },
select: { id: true, ownerId: true },
});
if (!recipe || recipe.ownerId !== ownerId) {
throw new NotFoundException(`Recipe with id ${id} not found`);
}
const recipe = await this.findRecipeByIdOrThrow(id);
this.assertRecipeOwnedByUser(recipe, ownerId, id);
const targetUser = await this.prisma.user.findUnique({
where: { username },