feat: implement user-specific inventory management with security checks
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-07 11:58:00 +02:00
parent 4affb03504
commit 17893824d5
8 changed files with 181 additions and 31 deletions
+24 -19
View File
@@ -1,5 +1,5 @@
import { ConsumeInventoryDto } from './dto/consume-inventory.dto';
import { Injectable, NotFoundException } from '@nestjs/common';
import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { CreateInventoryDto } from './dto/create-inventory.dto';
@@ -32,24 +32,27 @@ export class InventoryService {
throw new NotFoundException(`Inventory item with id ${id} not found`);
}
private async findInventoryItemByIdOrThrow(id: number) {
private async findInventoryItemByIdOrThrow(id: number, userId: number) {
const existing = await this.prisma.inventoryItem.findUnique({ where: { id } });
if (!existing) {
this.throwInventoryItemNotFound(id);
}
if (existing.userId !== userId) {
throw new ForbiddenException(`Inventory item with id ${id} does not belong to current user`);
}
return existing;
}
private async ensureProductExists(productId: number) {
const product = await this.prisma.product.findUnique({ where: { id: productId } });
private async ensureProductExists(productId: number, userId: number) {
const product = await this.prisma.product.findFirst({ where: { id: productId, ownerId: userId } });
if (!product) {
throw new NotFoundException('Product not found');
throw new NotFoundException('Product not found for current user');
}
return product;
}
async findAll(query?: InventoryQuery) {
const where: Prisma.InventoryItemWhereInput = {};
async findAll(userId: number, query?: InventoryQuery) {
const where: Prisma.InventoryItemWhereInput = { userId };
const orderBy: Prisma.InventoryItemOrderByWithRelationInput[] = [];
if (query?.location) {
@@ -79,8 +82,8 @@ export class InventoryService {
});
}
async consume(id: number, data: ConsumeInventoryDto) {
const existing = await this.findInventoryItemByIdOrThrow(id);
async consume(id: number, userId: number, data: ConsumeInventoryDto) {
const existing = await this.findInventoryItemByIdOrThrow(id, userId);
const currentQuantity = Number(existing.quantity);
const newQuantity = Math.max(0, currentQuantity - data.amountUsed);
@@ -108,8 +111,8 @@ export class InventoryService {
});
}
async findConsumptionHistory(id: number) {
await this.findInventoryItemByIdOrThrow(id);
async findConsumptionHistory(id: number, userId: number) {
await this.findInventoryItemByIdOrThrow(id, userId);
return this.prisma.inventoryConsumption.findMany({
where: {
@@ -130,11 +133,12 @@ export class InventoryService {
},
});
}
async findExpiring() {
async findExpiring(userId: number) {
const now = new Date();
return this.prisma.inventoryItem.findMany({
where: {
userId,
bestBeforeDate: {
not: null,
gte: now,
@@ -147,12 +151,13 @@ export class InventoryService {
});
}
async create(data: CreateInventoryDto) {
await this.ensureProductExists(data.productId);
async create(userId: number, data: CreateInventoryDto) {
await this.ensureProductExists(data.productId, userId);
return this.prisma.inventoryItem.create({
data: {
...data,
userId,
quantity: new Prisma.Decimal(data.quantity),
location: data.location?.trim() || undefined,
brand: data.brand?.trim() || undefined,
@@ -173,11 +178,11 @@ export class InventoryService {
});
}
async update(id: number, data: UpdateInventoryDto) {
await this.findInventoryItemByIdOrThrow(id);
async update(id: number, userId: number, data: UpdateInventoryDto) {
await this.findInventoryItemByIdOrThrow(id, userId);
if (typeof data.productId === 'number') {
await this.ensureProductExists(data.productId);
await this.ensureProductExists(data.productId, userId);
}
const updateData: Prisma.InventoryItemUpdateInput = {};
@@ -241,8 +246,8 @@ export class InventoryService {
});
}
async remove(id: number) {
await this.findInventoryItemByIdOrThrow(id);
async remove(id: number, userId: number) {
await this.findInventoryItemByIdOrThrow(id, userId);
return this.prisma.inventoryItem.delete({ where: { id } });
}
}