Refactor code structure for improved readability and maintainability
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-06 07:37:59 +02:00
parent e4f201ea36
commit 969dafdbc6
273 changed files with 11357 additions and 39 deletions
+3
View File
@@ -0,0 +1,3 @@
export declare class AiCategorizeBulkDto {
productIds?: number[];
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiCategorizeBulkDto = void 0;
const class_validator_1 = require("class-validator");
class AiCategorizeBulkDto {
}
exports.AiCategorizeBulkDto = AiCategorizeBulkDto;
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsArray)(),
(0, class_validator_1.IsInt)({ each: true }),
__metadata("design:type", Array)
], AiCategorizeBulkDto.prototype, "productIds", void 0);
//# sourceMappingURL=ai-categorize-bulk.dto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ai-categorize-bulk.dto.js","sourceRoot":"","sources":["../../../src/products/dto/ai-categorize-bulk.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA6D;AAE7D,MAAa,mBAAmB;CAK/B;AALD,kDAKC;AADC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,uBAAK,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;uDACA"}
@@ -0,0 +1,4 @@
export declare class BulkUpdateProductsDto {
ids: number[];
categoryId?: number | null;
}
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BulkUpdateProductsDto = void 0;
const class_validator_1 = require("class-validator");
class BulkUpdateProductsDto {
}
exports.BulkUpdateProductsDto = BulkUpdateProductsDto;
__decorate([
(0, class_validator_1.IsArray)(),
(0, class_validator_1.ArrayMinSize)(1),
(0, class_validator_1.IsInt)({ each: true }),
__metadata("design:type", Array)
], BulkUpdateProductsDto.prototype, "ids", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
__metadata("design:type", Object)
], BulkUpdateProductsDto.prototype, "categoryId", void 0);
//# sourceMappingURL=bulk-update-products.dto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"bulk-update-products.dto.js","sourceRoot":"","sources":["../../../src/products/dto/bulk-update-products.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAqF;AAErF,MAAa,qBAAqB;CASjC;AATD,sDASC;AALC;IAHC,IAAA,yBAAO,GAAE;IACT,IAAA,8BAAY,EAAC,CAAC,CAAC;IACf,IAAA,uBAAK,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;kDACR;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;yDACgB"}
+4
View File
@@ -0,0 +1,4 @@
export declare class CreateProductDto {
name: string;
categoryId?: number;
}
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateProductDto = void 0;
const class_validator_1 = require("class-validator");
class CreateProductDto {
}
exports.CreateProductDto = CreateProductDto;
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], CreateProductDto.prototype, "name", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsInt)(),
__metadata("design:type", Number)
], CreateProductDto.prototype, "categoryId", void 0);
//# sourceMappingURL=create-product.dto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"create-product.dto.js","sourceRoot":"","sources":["../../../src/products/dto/create-product.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAqF;AAErF,MAAa,gBAAgB;CAS5B;AATD,4CASC;AALC;IAHC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,EAAC,GAAG,CAAC;;8CACD;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;oDACY"}
+4
View File
@@ -0,0 +1,4 @@
export declare class MergeProductsDto {
sourceProductId: number;
targetProductId: number;
}
+25
View File
@@ -0,0 +1,25 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MergeProductsDto = void 0;
const class_validator_1 = require("class-validator");
class MergeProductsDto {
}
exports.MergeProductsDto = MergeProductsDto;
__decorate([
(0, class_validator_1.IsInt)(),
__metadata("design:type", Number)
], MergeProductsDto.prototype, "sourceProductId", void 0);
__decorate([
(0, class_validator_1.IsInt)(),
__metadata("design:type", Number)
], MergeProductsDto.prototype, "targetProductId", void 0);
//# sourceMappingURL=merge-products.dto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"merge-products.dto.js","sourceRoot":"","sources":["../../../src/products/dto/merge-products.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAwC;AAExC,MAAa,gBAAgB;CAM5B;AAND,4CAMC;AAJC;IADC,IAAA,uBAAK,GAAE;;yDACiB;AAGzB;IADC,IAAA,uBAAK,GAAE;;yDACiB"}
+3
View File
@@ -0,0 +1,3 @@
export declare class SetProductStatusDto {
status: string;
}
+21
View File
@@ -0,0 +1,21 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SetProductStatusDto = void 0;
const class_validator_1 = require("class-validator");
class SetProductStatusDto {
}
exports.SetProductStatusDto = SetProductStatusDto;
__decorate([
(0, class_validator_1.IsIn)(['active', 'rejected']),
__metadata("design:type", String)
], SetProductStatusDto.prototype, "status", void 0);
//# sourceMappingURL=set-product-status.dto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"set-product-status.dto.js","sourceRoot":"","sources":["../../../src/products/dto/set-product-status.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAuC;AAEvC,MAAa,mBAAmB;CAG/B;AAHD,kDAGC;AADC;IADC,IAAA,sBAAI,EAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;;mDACd"}
+3
View File
@@ -0,0 +1,3 @@
export declare class SetTagsDto {
tags: string[];
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SetTagsDto = void 0;
const class_validator_1 = require("class-validator");
class SetTagsDto {
}
exports.SetTagsDto = SetTagsDto;
__decorate([
(0, class_validator_1.IsArray)(),
(0, class_validator_1.IsString)({ each: true }),
(0, class_validator_1.MaxLength)(100, { each: true }),
__metadata("design:type", Array)
], SetTagsDto.prototype, "tags", void 0);
//# sourceMappingURL=set-tags.dto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"set-tags.dto.js","sourceRoot":"","sources":["../../../src/products/dto/set-tags.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA+D;AAE/D,MAAa,UAAU;CAKtB;AALD,gCAKC;AADC;IAHC,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxB,IAAA,2BAAS,EAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;wCACf"}
@@ -0,0 +1,3 @@
export declare class UpdateCanonicalNameDto {
canonicalName: string;
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateCanonicalNameDto = void 0;
const class_validator_1 = require("class-validator");
class UpdateCanonicalNameDto {
}
exports.UpdateCanonicalNameDto = UpdateCanonicalNameDto;
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], UpdateCanonicalNameDto.prototype, "canonicalName", void 0);
//# sourceMappingURL=update-canonical-name.dto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"update-canonical-name.dto.js","sourceRoot":"","sources":["../../../src/products/dto/update-canonical-name.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAkE;AAElE,MAAa,sBAAsB;CAKlC;AALD,wDAKC;AADC;IAHC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,EAAC,GAAG,CAAC;;6DACQ"}
+7
View File
@@ -0,0 +1,7 @@
export declare class UpdateProductDto {
name?: string;
canonicalName?: string;
category?: string;
subcategory?: string;
categoryId?: number | null;
}
+47
View File
@@ -0,0 +1,47 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateProductDto = void 0;
const class_validator_1 = require("class-validator");
class UpdateProductDto {
}
exports.UpdateProductDto = UpdateProductDto;
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], UpdateProductDto.prototype, "name", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], UpdateProductDto.prototype, "canonicalName", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], UpdateProductDto.prototype, "category", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
(0, class_validator_1.MaxLength)(191),
__metadata("design:type", String)
], UpdateProductDto.prototype, "subcategory", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
__metadata("design:type", Object)
], UpdateProductDto.prototype, "categoryId", void 0);
//# sourceMappingURL=update-product.dto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"update-product.dto.js","sourceRoot":"","sources":["../../../src/products/dto/update-product.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAwF;AAExF,MAAa,gBAAgB;CAyB5B;AAzBD,4CAyBC;AApBC;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,EAAC,GAAG,CAAC;;8CACD;AAKd;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,GAAG,CAAC;;uDACQ;AAKvB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,GAAG,CAAC;;kDACG;AAKlB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,GAAG,CAAC;;qDACM;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACgB"}
+9
View File
@@ -0,0 +1,9 @@
export declare class UpsertNutritionDto {
calories?: number;
protein?: number;
fat?: number;
carbohydrates?: number;
salt?: number;
sugar?: number;
fiber?: number;
}
+59
View File
@@ -0,0 +1,59 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpsertNutritionDto = void 0;
const class_validator_1 = require("class-validator");
class UpsertNutritionDto {
}
exports.UpsertNutritionDto = UpsertNutritionDto;
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "calories", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "protein", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "fat", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "carbohydrates", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "salt", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "sugar", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], UpsertNutritionDto.prototype, "fiber", void 0);
//# sourceMappingURL=upsert-nutrition.dto.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"upsert-nutrition.dto.js","sourceRoot":"","sources":["../../../src/products/dto/upsert-nutrition.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA4D;AAE5D,MAAa,kBAAkB;CAmC9B;AAnCD,gDAmCC;AA/BC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;oDACW;AAKlB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;mDACU;AAKjB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;+CACM;AAKb;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;yDACgB;AAKvB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;gDACO;AAKd;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;iDACQ;AAKf;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;iDACQ"}
+471
View File
@@ -0,0 +1,471 @@
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { ProductsService } from './products.service';
import { MergeProductsDto } from './dto/merge-products.dto';
import { UpdateCanonicalNameDto } from './dto/update-canonical-name.dto';
import { SetTagsDto } from './dto/set-tags.dto';
import { UpsertNutritionDto } from './dto/upsert-nutrition.dto';
import { BulkUpdateProductsDto } from './dto/bulk-update-products.dto';
import { AiCategorizeBulkDto } from './dto/ai-categorize-bulk.dto';
import { SetProductStatusDto } from './dto/set-product-status.dto';
import { AiService } from '../ai/ai.service';
import { CategoriesService } from '../categories/categories.service';
export declare class ProductsController {
private readonly productsService;
private readonly aiService;
private readonly categoriesService;
constructor(productsService: ProductsService, aiService: AiService, categoriesService: CategoriesService);
findAll(tag?: string): Promise<({
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
categoryRef: ({
parent: ({
parent: {
name: string;
id: number;
parentId: number | null;
} | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
})[]>;
findAllTags(): Promise<{
name: string;
id: number;
}[]>;
findDuplicates(): Promise<{
normalizedName: string;
count: number;
products: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[];
}[]>;
previewMerge(sourceProductId: number, targetProductId: number): Promise<{
source: {
inventoryCount: number;
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
target: {
inventoryCount: number;
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
outcome: {
inventoryItemsToMove: number;
sourceWillBeSoftDeleted: boolean;
targetWillRemainActive: boolean;
};
}>;
backfillCanonical(): Promise<{
message: string;
updatedCount: number;
products: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[];
}>;
findPending(): Promise<({
owner: {
id: number;
username: string;
};
categoryRef: ({
parent: {
name: string;
id: number;
parentId: number | null;
} | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
})[]>;
aiCategorizeBulk(body: AiCategorizeBulkDto): Promise<{
productId: number;
productName: string;
suggestion: object;
}[]>;
findDeleted(): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[]>;
findMine(req: {
user: {
id: number;
};
}): Promise<{
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
}[]>;
findOne(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
createPrivate(body: CreateProductDto, req: {
user: {
id: number;
};
}): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
suggestCategory(id: number): Promise<import("../ai/ai.service").CategorySuggestion>;
create(body: CreateProductDto, req: {
user: {
id: number;
};
}): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
createPending(body: CreateProductDto, req: {
user: {
id: number;
};
}): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
merge(body: MergeProductsDto): Promise<{
message: string;
sourceProductId: number;
targetProductId: number;
movedInventoryCount: number;
softDeletedSource: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
}>;
updateCanonicalName(id: number, body: UpdateCanonicalNameDto): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
setTags(id: number, body: SetTagsDto): Promise<({
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}) | null>;
upsertNutrition(id: number, body: UpsertNutritionDto): Promise<{
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
}>;
update(id: number, body: UpdateProductDto): Promise<{
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
permanentDelete(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
remove(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
setStatus(id: number, body: SetProductStatusDto): import(".prisma/client").Prisma.Prisma__ProductClient<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>;
restore(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
resetAll(): Promise<{
ok: boolean;
}>;
bulkUpdate(body: BulkUpdateProductsDto): Promise<{
updated: number;
}>;
}
+328
View File
@@ -0,0 +1,328 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProductsController = void 0;
const common_1 = require("@nestjs/common");
const throttler_1 = require("@nestjs/throttler");
const public_decorator_1 = require("../auth/decorators/public.decorator");
const create_product_dto_1 = require("./dto/create-product.dto");
const update_product_dto_1 = require("./dto/update-product.dto");
const products_service_1 = require("./products.service");
const merge_products_dto_1 = require("./dto/merge-products.dto");
const update_canonical_name_dto_1 = require("./dto/update-canonical-name.dto");
const set_tags_dto_1 = require("./dto/set-tags.dto");
const upsert_nutrition_dto_1 = require("./dto/upsert-nutrition.dto");
const bulk_update_products_dto_1 = require("./dto/bulk-update-products.dto");
const ai_categorize_bulk_dto_1 = require("./dto/ai-categorize-bulk.dto");
const set_product_status_dto_1 = require("./dto/set-product-status.dto");
const roles_decorator_1 = require("../auth/decorators/roles.decorator");
const ai_service_1 = require("../ai/ai.service");
const categories_service_1 = require("../categories/categories.service");
const premium_or_admin_guard_1 = require("../auth/premium-or-admin.guard");
let ProductsController = class ProductsController {
constructor(productsService, aiService, categoriesService) {
this.productsService = productsService;
this.aiService = aiService;
this.categoriesService = categoriesService;
}
findAll(tag) {
return this.productsService.findAll({ tag });
}
findAllTags() {
return this.productsService.findAllTags();
}
findDuplicates() {
return this.productsService.findDuplicateCandidates();
}
previewMerge(sourceProductId, targetProductId) {
return this.productsService.previewMerge(sourceProductId, targetProductId);
}
backfillCanonical() {
return this.productsService.backfillCanonicalNames();
}
findPending() {
return this.productsService.findPending();
}
aiCategorizeBulk(body) {
return this.productsService.aiCategorizeBulk(body.productIds);
}
findDeleted() {
return this.productsService.findDeleted();
}
findMine(req) {
return this.productsService.findByOwner(req.user.id);
}
findOne(id) {
return this.productsService.findOne(id);
}
createPrivate(body, req) {
return this.productsService.createPrivate(body, req.user.id);
}
async suggestCategory(id) {
const product = await this.productsService.findOne(id);
const categories = await this.categoriesService.findFlattened();
return this.aiService.suggestCategory(product.canonicalName ?? product.name, categories);
}
create(body, req) {
return this.productsService.create(body, req.user.id);
}
createPending(body, req) {
return this.productsService.createPending(body, req.user.id);
}
merge(body) {
return this.productsService.merge(body.sourceProductId, body.targetProductId);
}
updateCanonicalName(id, body) {
return this.productsService.updateCanonicalName(id, body.canonicalName);
}
setTags(id, body) {
return this.productsService.setTags(id, body.tags);
}
upsertNutrition(id, body) {
return this.productsService.upsertNutrition(id, body);
}
update(id, body) {
return this.productsService.update(id, body);
}
permanentDelete(id) {
return this.productsService.permanentDelete(id);
}
remove(id) {
return this.productsService.remove(id);
}
setStatus(id, body) {
return this.productsService.setStatus(id, body.status);
}
restore(id) {
return this.productsService.restore(id);
}
resetAll() {
return this.productsService.resetAll();
}
bulkUpdate(body) {
return this.productsService.bulkUpdate(body.ids, { categoryId: body.categoryId });
}
};
exports.ProductsController = ProductsController;
__decorate([
(0, public_decorator_1.Public)(),
(0, common_1.Get)(),
__param(0, (0, common_1.Query)('tag')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findAll", null);
__decorate([
(0, public_decorator_1.Public)(),
(0, common_1.Get)('tags'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findAllTags", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Get)('duplicates'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findDuplicates", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Get)('merge-preview'),
__param(0, (0, common_1.Query)('sourceProductId', common_1.ParseIntPipe)),
__param(1, (0, common_1.Query)('targetProductId', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "previewMerge", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)('backfill-canonical'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "backfillCanonical", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Get)('pending'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findPending", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)('ai-categorize-bulk'),
(0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 5 } }),
(0, common_1.HttpCode)(200),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [ai_categorize_bulk_dto_1.AiCategorizeBulkDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "aiCategorizeBulk", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Get)('deleted'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findDeleted", null);
__decorate([
(0, common_1.Get)('mine'),
__param(0, (0, common_1.Request)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findMine", null);
__decorate([
(0, common_1.Get)(':id'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "findOne", null);
__decorate([
(0, common_1.Post)('private'),
__param(0, (0, common_1.Body)()),
__param(1, (0, common_1.Request)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [create_product_dto_1.CreateProductDto, Object]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "createPrivate", null);
__decorate([
(0, common_1.UseGuards)(premium_or_admin_guard_1.PremiumOrAdminGuard),
(0, common_1.Get)(':id/suggest-category'),
(0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 20 } }),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], ProductsController.prototype, "suggestCategory", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)(),
__param(0, (0, common_1.Body)()),
__param(1, (0, common_1.Request)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [create_product_dto_1.CreateProductDto, Object]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "create", null);
__decorate([
(0, common_1.Post)('pending'),
__param(0, (0, common_1.Body)()),
__param(1, (0, common_1.Request)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [create_product_dto_1.CreateProductDto, Object]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "createPending", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)('merge'),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [merge_products_dto_1.MergeProductsDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "merge", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Patch)(':id/canonical-name'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, update_canonical_name_dto_1.UpdateCanonicalNameDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "updateCanonicalName", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Put)(':id/tags'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, set_tags_dto_1.SetTagsDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "setTags", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Put)(':id/nutrition'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, upsert_nutrition_dto_1.UpsertNutritionDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "upsertNutrition", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Patch)(':id'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, update_product_dto_1.UpdateProductDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "update", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Delete)(':id/permanent'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "permanentDelete", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Delete)(':id'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "remove", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Patch)(':id/status'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, set_product_status_dto_1.SetProductStatusDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "setStatus", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)(':id/restore'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "restore", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)('reset-all'),
(0, common_1.HttpCode)(200),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "resetAll", null);
__decorate([
(0, roles_decorator_1.Roles)('admin'),
(0, common_1.Post)('bulk-update'),
(0, common_1.HttpCode)(200),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [bulk_update_products_dto_1.BulkUpdateProductsDto]),
__metadata("design:returntype", void 0)
], ProductsController.prototype, "bulkUpdate", null);
exports.ProductsController = ProductsController = __decorate([
(0, common_1.Controller)('products'),
__metadata("design:paramtypes", [products_service_1.ProductsService,
ai_service_1.AiService,
categories_service_1.CategoriesService])
], ProductsController);
//# sourceMappingURL=products.controller.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
export declare class ProductsModule {
}
+25
View File
@@ -0,0 +1,25 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProductsModule = void 0;
const common_1 = require("@nestjs/common");
const products_controller_1 = require("./products.controller");
const products_service_1 = require("./products.service");
const ai_module_1 = require("../ai/ai.module");
const categories_module_1 = require("../categories/categories.module");
let ProductsModule = class ProductsModule {
};
exports.ProductsModule = ProductsModule;
exports.ProductsModule = ProductsModule = __decorate([
(0, common_1.Module)({
imports: [ai_module_1.AiModule, categories_module_1.CategoriesModule],
controllers: [products_controller_1.ProductsController],
providers: [products_service_1.ProductsService],
})
], ProductsModule);
//# sourceMappingURL=products.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"products.module.js","sourceRoot":"","sources":["../../src/products/products.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,+DAA2D;AAC3D,yDAAqD;AACrD,+CAA2C;AAC3C,uEAAmE;AAO5D,IAAM,cAAc,GAApB,MAAM,cAAc;CAAG,CAAA;AAAjB,wCAAc;yBAAd,cAAc;IAL1B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,oBAAQ,EAAE,oCAAgB,CAAC;QACrC,WAAW,EAAE,CAAC,wCAAkB,CAAC;QACjC,SAAS,EAAE,CAAC,kCAAe,CAAC;KAC7B,CAAC;GACW,cAAc,CAAG"}
+458
View File
@@ -0,0 +1,458 @@
import { PrismaService } from '../prisma/prisma.service';
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';
export declare class ProductsService {
private readonly prisma;
private readonly aiService;
private readonly categoriesService;
constructor(prisma: PrismaService, aiService: AiService, categoriesService: CategoriesService);
findAll(filters?: {
tag?: string;
}): Promise<({
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
categoryRef: ({
parent: ({
parent: {
name: string;
id: number;
parentId: number | null;
} | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
})[]>;
findByOwner(userId: number): Promise<{
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
}[]>;
createPrivate(data: CreateProductDto, userId: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
findDuplicateCandidates(): Promise<{
normalizedName: string;
count: number;
products: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[];
}[]>;
findOne(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
create(data: CreateProductDto, ownerId?: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
update(id: number, data: UpdateProductDto): Promise<{
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
updateCanonicalName(id: number, canonicalName: string): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
findDeleted(): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[]>;
remove(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
permanentDelete(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
restore(id: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
private findProductByIdOrThrow;
merge(sourceProductId: number, targetProductId: number): Promise<{
message: string;
sourceProductId: number;
targetProductId: number;
movedInventoryCount: number;
softDeletedSource: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
}>;
previewMerge(sourceProductId: number, targetProductId: number): Promise<{
source: {
inventoryCount: number;
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
target: {
inventoryCount: number;
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
};
outcome: {
inventoryItemsToMove: number;
sourceWillBeSoftDeleted: boolean;
targetWillRemainActive: boolean;
};
}>;
backfillCanonicalNames(): Promise<{
message: string;
updatedCount: number;
products: {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}[];
}>;
setTags(productId: number, tagNames: string[]): Promise<({
nutrition: {
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
} | null;
tags: ({
tag: {
name: string;
id: number;
};
} & {
productId: number;
tagId: number;
})[];
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}) | null>;
upsertNutrition(productId: number, data: UpsertNutritionDto): Promise<{
calories: number | null;
protein: number | null;
fat: number | null;
carbohydrates: number | null;
salt: number | null;
sugar: number | null;
fiber: number | null;
id: number;
productId: number;
}>;
findAllTags(): Promise<{
name: string;
id: number;
}[]>;
resetAll(): Promise<{
ok: boolean;
}>;
bulkUpdate(ids: number[], data: {
categoryId?: number | null;
}): Promise<{
updated: number;
}>;
findUncategorized(): Promise<{
id: number;
name: string;
canonicalName: string | null;
}[]>;
aiCategorizeBulk(productIds?: number[]): Promise<{
productId: number;
productName: string;
suggestion: object;
}[]>;
findPending(): Promise<({
owner: {
id: number;
username: string;
};
categoryRef: ({
parent: {
name: string;
id: number;
parentId: number | null;
} | null;
} & {
name: string;
id: number;
parentId: number | null;
}) | null;
} & {
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
})[]>;
createPending(data: CreateProductDto, userId: number): Promise<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}>;
setStatus(id: number, status: string): import(".prisma/client").Prisma.Prisma__ProductClient<{
category: string | null;
status: string;
name: string;
categoryId: number | null;
canonicalName: string | null;
id: number;
normalizedName: string;
isActive: boolean;
deletedAt: Date | null;
createdAt: Date;
updatedAt: Date;
ownerId: number;
isPrivate: boolean;
}, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>;
}
+425
View File
@@ -0,0 +1,425 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProductsService = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../prisma/prisma.service");
const normalize_name_1 = require("../common/utils/normalize-name");
const ai_service_1 = require("../ai/ai.service");
const categories_service_1 = require("../categories/categories.service");
let ProductsService = class ProductsService {
constructor(prisma, aiService, categoriesService) {
this.prisma = prisma;
this.aiService = aiService;
this.categoriesService = categoriesService;
}
async findAll(filters) {
return this.prisma.product.findMany({
where: {
isActive: true,
isPrivate: false,
...(filters?.tag
? { tags: { some: { tag: { name: filters.tag } } } }
: {}),
},
include: {
tags: { include: { tag: true } },
nutrition: true,
categoryRef: { include: { parent: { include: { parent: true } } } },
},
orderBy: { name: 'asc' },
});
}
async findByOwner(userId) {
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, userId) {
const name = data.name.trim();
const normalizedName = `private:${userId}:${(0, normalize_name_1.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: {
isActive: true,
},
orderBy: {
name: 'asc',
},
});
const grouped = new Map();
for (const product of products) {
const key = product.normalizedName;
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key).push(product);
}
return Array.from(grouped.entries())
.filter(([, items]) => items.length > 1)
.map(([normalizedName, items]) => ({
normalizedName,
count: items.length,
products: items,
}));
}
async findOne(id) {
const product = await this.prisma.product.findUnique({
where: { id },
});
if (!product) {
throw new common_1.NotFoundException(`Product with id ${id} not found`);
}
return product;
}
async create(data, ownerId) {
const name = data.name.trim();
const normalizedName = (0, normalize_name_1.normalizeName)(name);
const existing = await this.prisma.product.findUnique({
where: { normalizedName },
});
if (existing) {
if (!existing.isActive) {
return this.prisma.product.update({
where: { id: existing.id },
data: {
isActive: true,
deletedAt: null,
name,
canonicalName: name,
},
});
}
return existing;
}
if (!ownerId) {
throw new Error('ownerId är obligatorisk för att skapa en produkt');
}
return this.prisma.product.create({
data: {
ownerId,
name,
normalizedName,
canonicalName: name,
isActive: true,
deletedAt: null,
...(data.categoryId != null ? { categoryId: data.categoryId } : {}),
},
});
}
async update(id, data) {
await this.findOne(id);
const updateData = {};
if (typeof data.name === 'string') {
const name = data.name.trim();
const normalizedName = (0, normalize_name_1.normalizeName)(name);
const existing = await this.prisma.product.findUnique({
where: { normalizedName },
});
if (existing && existing.id !== id) {
throw new Error('Det finns redan en annan produkt med detta namn. Välj ett unikt namn.');
}
updateData.name = name;
updateData.normalizedName = normalizedName;
}
if (typeof data.canonicalName === 'string') {
updateData.canonicalName = data.canonicalName.trim() || undefined;
}
if (typeof data.category === 'string') {
updateData.category = data.category.trim() || null;
}
if ('categoryId' in data) {
updateData.categoryId = data.categoryId ?? null;
}
return this.prisma.product.update({
where: { id },
data: updateData,
include: { tags: { include: { tag: true } }, nutrition: true },
});
}
async updateCanonicalName(id, canonicalName) {
await this.findOne(id);
const cleaned = canonicalName.trim();
return this.prisma.product.update({
where: { id },
data: {
canonicalName: cleaned,
},
});
}
async findDeleted() {
return this.prisma.product.findMany({
where: { isActive: false },
orderBy: { deletedAt: 'desc' },
});
}
async remove(id) {
await this.findOne(id);
return this.prisma.product.update({
where: { id },
data: {
isActive: false,
deletedAt: new Date(),
},
});
}
async permanentDelete(id) {
await this.findOne(id);
await this.prisma.productTag.deleteMany({ where: { productId: id } });
await this.prisma.userProduct.deleteMany({ where: { productId: id } });
return this.prisma.product.delete({ where: { id } });
}
async restore(id) {
const product = await this.findOne(id);
if (product.isActive) {
return product;
}
return this.prisma.product.update({
where: { id },
data: {
isActive: true,
deletedAt: null,
},
});
}
async findProductByIdOrThrow(id, label) {
const product = await this.prisma.product.findUnique({ where: { id } });
if (!product) {
throw new common_1.NotFoundException(`${label} product with id ${id} not found`);
}
return product;
}
async merge(sourceProductId, targetProductId) {
if (sourceProductId === targetProductId) {
throw new Error('sourceProductId och targetProductId kan inte vara samma');
}
const source = await this.findProductByIdOrThrow(sourceProductId, 'Source');
const target = await this.findProductByIdOrThrow(targetProductId, 'Target');
return this.prisma.$transaction(async (tx) => {
const movedInventoryCount = await tx.inventoryItem.updateMany({
where: { productId: sourceProductId },
data: { productId: targetProductId },
});
const softDeletedSource = await tx.product.update({
where: { id: sourceProductId },
data: {
isActive: false,
deletedAt: new Date(),
},
});
return {
message: 'Products merged successfully',
sourceProductId,
targetProductId,
movedInventoryCount: movedInventoryCount.count,
softDeletedSource,
};
});
}
async previewMerge(sourceProductId, targetProductId) {
if (sourceProductId === targetProductId) {
throw new Error('sourceProductId och targetProductId kan inte vara samma');
}
const [source, target, sourceInventoryCount, targetInventoryCount] = await Promise.all([
this.findProductByIdOrThrow(sourceProductId, 'Source'),
this.findProductByIdOrThrow(targetProductId, 'Target'),
this.prisma.inventoryItem.count({
where: { productId: sourceProductId },
}),
this.prisma.inventoryItem.count({
where: { productId: targetProductId },
}),
]);
return {
source: {
...source,
inventoryCount: sourceInventoryCount,
},
target: {
...target,
inventoryCount: targetInventoryCount,
},
outcome: {
inventoryItemsToMove: sourceInventoryCount,
sourceWillBeSoftDeleted: true,
targetWillRemainActive: true,
},
};
}
async backfillCanonicalNames() {
const products = await this.prisma.product.findMany({
where: {
canonicalName: null,
},
});
const results = await this.prisma.$transaction(products.map((product) => this.prisma.product.update({
where: { id: product.id },
data: {
canonicalName: product.name,
},
})));
return {
message: 'Canonical names backfilled successfully',
updatedCount: results.length,
products: results,
};
}
async setTags(productId, tagNames) {
await this.findOne(productId);
const tags = await this.prisma.$transaction(tagNames.map((name) => this.prisma.tag.upsert({
where: { name },
create: { name },
update: {},
})));
await this.prisma.productTag.deleteMany({ where: { productId } });
if (tags.length > 0) {
await this.prisma.productTag.createMany({
data: tags.map((tag) => ({ productId, tagId: tag.id })),
skipDuplicates: true,
});
}
return this.prisma.product.findUnique({
where: { id: productId },
include: { tags: { include: { tag: true } }, nutrition: true },
});
}
async upsertNutrition(productId, data) {
await this.findOne(productId);
return this.prisma.nutrition.upsert({
where: { productId },
create: { productId, ...data },
update: { ...data },
});
}
async findAllTags() {
return this.prisma.tag.findMany({ orderBy: { name: 'asc' } });
}
async resetAll() {
await this.prisma.$transaction([
this.prisma.receiptAlias.deleteMany(),
this.prisma.inventoryConsumption.deleteMany(),
this.prisma.inventoryItem.deleteMany(),
this.prisma.pantryItem.deleteMany(),
this.prisma.nutrition.deleteMany(),
this.prisma.productTag.deleteMany(),
this.prisma.tag.deleteMany(),
this.prisma.userProduct.deleteMany(),
this.prisma.recipeIngredient.deleteMany(),
this.prisma.product.deleteMany(),
]);
return { ok: true };
}
async bulkUpdate(ids, data) {
const updateData = {};
if ('categoryId' in data) {
updateData.categoryId = data.categoryId;
}
if (Object.keys(updateData).length === 0)
return { updated: 0 };
await this.prisma.product.updateMany({ where: { id: { in: ids } }, data: updateData });
return { updated: ids.length };
}
async findUncategorized() {
return this.prisma.product.findMany({
where: { isActive: true, categoryId: null, status: 'active' },
select: { id: true, name: true, canonicalName: true },
orderBy: { name: 'asc' },
});
}
async aiCategorizeBulk(productIds) {
const categories = await this.categoriesService.findFlattened();
let products;
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 = [];
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' },
include: {
categoryRef: { include: { parent: true } },
owner: { select: { id: true, username: true } },
},
orderBy: { createdAt: 'desc' },
});
}
async createPending(data, userId) {
const name = data.name.trim();
const normalizedName = (0, normalize_name_1.normalizeName)(name);
const existing = await this.prisma.product.findUnique({
where: { normalizedName },
});
if (existing) {
if (existing.isActive && existing.status === 'active') {
return existing;
}
if (existing.status === 'pending') {
return existing;
}
}
return this.prisma.product.create({
data: {
name,
normalizedName,
canonicalName: name,
isActive: false,
status: 'pending',
ownerId: userId,
},
});
}
setStatus(id, status) {
return this.prisma.product.update({ where: { id }, data: { status } });
}
};
exports.ProductsService = ProductsService;
exports.ProductsService = ProductsService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
ai_service_1.AiService,
categories_service_1.CategoriesService])
], ProductsService);
//# sourceMappingURL=products.service.js.map
File diff suppressed because one or more lines are too long