feat: implement AI categorization for products and add premium access guard

This commit is contained in:
Nils-Johan Gynther
2026-04-21 13:55:12 +02:00
parent 83722123d2
commit 864c84d2e5
5 changed files with 73 additions and 40 deletions
+26 -1
View File
@@ -4,10 +4,16 @@ import { normalizeName } from '../common/utils/normalize-name';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { UpsertNutritionDto } from './dto/upsert-nutrition.dto';
import { AiService } from '../ai/ai.service';
import { CategoriesService } from '../categories/categories.service';
@Injectable()
export class ProductsService {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly aiService: AiService,
private readonly categoriesService: CategoriesService,
) {}
async findAll(filters?: { tag?: string; subcategory?: string }) {
return this.prisma.product.findMany({
@@ -423,6 +429,25 @@ export class ProductsService {
});
}
async aiCategorizeBulk(productIds?: number[]): Promise<{ productId: number; productName: string; suggestion: object }[]> {
const categories = await this.categoriesService.findFlattened();
let products: { id: number; name: string }[];
if (productIds && productIds.length > 0) {
const found = await Promise.all(productIds.map((id) => this.findOne(id)));
products = found.map((p) => ({ id: p.id, name: p.canonicalName ?? p.name }));
} else {
products = await this.findUncategorized();
}
const results: { productId: number; productName: string; suggestion: object }[] = [];
for (const product of products) {
const suggestion = await this.aiService.suggestCategory(product.name, categories);
results.push({ productId: product.id, productName: product.name, suggestion });
}
return results;
}
async findPending() {
return this.prisma.product.findMany({
where: { status: 'pending' },