feat(auth): implement user authentication with JWT and NextAuth
- Added user registration and login functionality with JWT authentication. - Created auth controller, service, and module in the backend. - Implemented user model and user products management. - Integrated NextAuth for session management on the frontend. - Added middleware for protecting routes and handling public access. - Updated frontend API routes to include authorization headers. - Enhanced recipe and user product models to support ownership and visibility. - Created registration and login pages in the frontend. - Added necessary types for NextAuth session management.
This commit is contained in:
@@ -290,27 +290,45 @@ export class RecipesService {
|
||||
};
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
async findAll(userId: number) {
|
||||
return this.prisma.recipe.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ isPublic: true },
|
||||
{ ownerId: userId },
|
||||
{ shares: { some: { userId } } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
ingredients: {
|
||||
include: {
|
||||
product: { include: { nutrition: true } },
|
||||
},
|
||||
},
|
||||
owner: { select: { id: true, username: true } },
|
||||
shares: { select: { userId: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const recipe = await this.prisma.recipe.findUnique({
|
||||
where: { id },
|
||||
async findOne(id: number, userId: number) {
|
||||
const recipe = await this.prisma.recipe.findFirst({
|
||||
where: {
|
||||
id,
|
||||
OR: [
|
||||
{ isPublic: true },
|
||||
{ ownerId: userId },
|
||||
{ shares: { some: { userId } } },
|
||||
],
|
||||
},
|
||||
include: {
|
||||
ingredients: {
|
||||
include: {
|
||||
product: { include: { nutrition: true } },
|
||||
},
|
||||
},
|
||||
owner: { select: { id: true, username: true } },
|
||||
shares: { select: { userId: true } },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -321,8 +339,8 @@ export class RecipesService {
|
||||
return recipe;
|
||||
}
|
||||
|
||||
async update(id: number, updateRecipeDto: CreateRecipeDto) {
|
||||
// Verifiera att receptet finns
|
||||
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 },
|
||||
});
|
||||
@@ -331,6 +349,11 @@ export class RecipesService {
|
||||
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`);
|
||||
}
|
||||
|
||||
// Ta bort gamla ingredienser
|
||||
await this.prisma.recipeIngredient.deleteMany({
|
||||
where: { recipeId: id },
|
||||
@@ -344,6 +367,7 @@ export class RecipesService {
|
||||
description: updateRecipeDto.description || null,
|
||||
instructions: updateRecipeDto.instructions || null,
|
||||
servings: updateRecipeDto.servings ?? null,
|
||||
...(updateRecipeDto.isPublic !== undefined && { isPublic: updateRecipeDto.isPublic }),
|
||||
...(updateRecipeDto.imageUrl !== undefined && { imageUrl: updateRecipeDto.imageUrl || null }),
|
||||
ingredients: {
|
||||
create: updateRecipeDto.ingredients.map((ingredient) => ({
|
||||
@@ -366,7 +390,7 @@ export class RecipesService {
|
||||
return recipe;
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
async remove(id: number, userId: number) {
|
||||
const existingRecipe = await this.prisma.recipe.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
@@ -375,6 +399,10 @@ export class RecipesService {
|
||||
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`);
|
||||
}
|
||||
|
||||
await this.prisma.recipeIngredient.deleteMany({ where: { recipeId: id } });
|
||||
await this.prisma.recipe.delete({ where: { id } });
|
||||
}
|
||||
@@ -394,7 +422,7 @@ export class RecipesService {
|
||||
});
|
||||
}
|
||||
|
||||
async create(createRecipeDto: CreateRecipeDto) {
|
||||
async create(createRecipeDto: CreateRecipeDto, userId: number) {
|
||||
// Om imageUrl är en extern URL — ladda ner och optimera
|
||||
let imageUrl: string | null = createRecipeDto.imageUrl || null;
|
||||
if (imageUrl && imageUrl.startsWith('http')) {
|
||||
@@ -413,6 +441,8 @@ export class RecipesService {
|
||||
instructions: createRecipeDto.instructions || null,
|
||||
imageUrl,
|
||||
servings: createRecipeDto.servings ?? null,
|
||||
ownerId: userId,
|
||||
isPublic: false,
|
||||
ingredients: {
|
||||
create: createRecipeDto.ingredients.map((ingredient) => ({
|
||||
productId: ingredient.productId,
|
||||
|
||||
Reference in New Issue
Block a user