feat: implement user-specific inventory management with security checks
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -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 } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user