feat: add isPrivate field to Product model and implement private product creation and retrieval

This commit is contained in:
Nils-Johan Gynther
2026-05-01 02:29:38 +02:00
parent 1fd910b561
commit 9ee061d5f3
10 changed files with 215 additions and 18 deletions
@@ -1,8 +1,12 @@
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';
export class CreateProductDto {
@IsString()
@IsNotEmpty()
@MaxLength(191)
name!: string;
@IsOptional()
@IsInt()
categoryId?: number;
}
@@ -94,12 +94,27 @@ export class ProductsController {
return this.productsService.findDeleted();
}
// Inloggad användares egna privata produkter (måste vara före :id)
@Get('mine')
findMine(@Request() req: { user: { id: number } }) {
return this.productsService.findByOwner(req.user.id);
}
// Tillgänglig för alla inloggade användare
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productsService.findOne(id);
}
// Skapa en privat produkt för den inloggade användaren
@Post('private')
createPrivate(
@Body() body: CreateProductDto,
@Request() req: { user: { id: number } },
) {
return this.productsService.createPrivate(body, req.user.id);
}
@UseGuards(PremiumOrAdminGuard)
@Get(':id/suggest-category')
@Throttle({ default: { ttl: 60_000, limit: 20 } })
+41
View File
@@ -19,6 +19,7 @@ export class ProductsService {
return this.prisma.product.findMany({
where: {
isActive: true,
isPrivate: false,
...(filters?.subcategory ? { subcategory: filters.subcategory } : {}),
...(filters?.tag
? { tags: { some: { tag: { name: filters.tag } } } }
@@ -33,6 +34,45 @@ export class ProductsService {
});
}
async findByOwner(userId: number) {
return this.prisma.product.findMany({
where: { ownerId: userId, isPrivate: true, isActive: true },
select: { id: true, name: true, canonicalName: true, categoryId: true },
orderBy: { name: 'asc' },
});
}
async createPrivate(data: CreateProductDto, userId: number) {
const name = data.name.trim();
// Privata produkters normalizedName är prefixade för att undvika kollision
const normalizedName = `private:${userId}:${normalizeName(name)}`;
const existing = await this.prisma.product.findUnique({
where: { normalizedName },
});
if (existing && existing.isActive) return existing;
if (existing) {
return this.prisma.product.update({
where: { id: existing.id },
data: { isActive: true, deletedAt: null, name, canonicalName: name },
});
}
return this.prisma.product.create({
data: {
name,
normalizedName,
canonicalName: name,
isActive: true,
isPrivate: true,
ownerId: userId,
...(data.categoryId != null ? { categoryId: data.categoryId } : {}),
},
});
}
async findDuplicateCandidates() {
const products = await this.prisma.product.findMany({
where: {
@@ -107,6 +147,7 @@ export class ProductsService {
canonicalName: name,
isActive: true,
deletedAt: null,
...(data.categoryId != null ? { categoryId: data.categoryId } : {}),
},
});
}