diff --git a/backend/dist/ai/ai.controller.d.ts b/backend/dist/ai/ai.controller.d.ts new file mode 100644 index 00000000..88bc29af --- /dev/null +++ b/backend/dist/ai/ai.controller.d.ts @@ -0,0 +1,12 @@ +export interface AiModelInfo { + id: string; + name: string; + description: string; + model: string; + path: string; + trigger: string; + access: string; +} +export declare class AiController { + getModels(): AiModelInfo[]; +} diff --git a/backend/dist/ai/ai.controller.js b/backend/dist/ai/ai.controller.js new file mode 100644 index 00000000..62d13e8d --- /dev/null +++ b/backend/dist/ai/ai.controller.js @@ -0,0 +1,79 @@ +"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.AiController = void 0; +const common_1 = require("@nestjs/common"); +const public_decorator_1 = require("../auth/decorators/public.decorator"); +const ai_service_1 = require("./ai.service"); +const RECEIPT_IMPORT_MODEL = 'mistral-small-2603'; +let AiController = class AiController { + getModels() { + return [ + { + id: 'receipt-pdf', + name: 'Kvittoimport — PDF-tolkning', + description: 'Extraherar varunamn, mängd och pris ur PDF-kvitton via textanalys.', + model: RECEIPT_IMPORT_MODEL, + path: '/import', + trigger: 'Vid uppladdning av PDF-kvitto', + access: 'Alla inloggade', + }, + { + id: 'receipt-image', + name: 'Kvittoimport — Bildtolkning', + description: 'Extraherar varunamn, mängd och pris ur kvittofoton via bildanalys.', + model: RECEIPT_IMPORT_MODEL, + path: '/import', + trigger: 'Vid uppladdning av kvittobild (JPEG, PNG, WebP, HEIC)', + access: 'Alla inloggade', + }, + { + id: 'receipt-category', + name: 'Kvittoimport — Kategorisuggestion', + description: 'För varor som inte matchas mot befintliga produkter visas ett AI-förslag på kategori som ledtråd.', + model: ai_service_1.AI_CATEGORIZATION_MODEL, + path: '/import', + trigger: 'Automatiskt efter kvittotolkning (om inga träffar hittas)', + access: 'Premium-användare + Admin', + }, + { + id: 'product-suggest', + name: 'AI-kategorisering per produkt', + description: 'Ger ett AI-förslag på kategori för en enskild produkt med säkerhetsindikation (hög/medel/låg).', + model: ai_service_1.AI_CATEGORIZATION_MODEL, + path: '/admin/products', + trigger: 'Manuell — klick på "✨ Fråga AI" i produktlistan', + access: 'Admin', + }, + { + id: 'product-bulk', + name: 'AI-bulk-kategorisering', + description: 'Analyserar alla okategoriserade produkter och presenterar förslag i ett bekräftelsemodal.', + model: ai_service_1.AI_CATEGORIZATION_MODEL, + path: '/admin/products', + trigger: 'Manuell — knappen "✨ AI-kategorisera okategoriserade"', + access: 'Admin', + }, + ]; + } +}; +exports.AiController = AiController; +__decorate([ + (0, common_1.Get)('models'), + (0, public_decorator_1.Public)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Array) +], AiController.prototype, "getModels", null); +exports.AiController = AiController = __decorate([ + (0, common_1.Controller)('ai') +], AiController); +//# sourceMappingURL=ai.controller.js.map \ No newline at end of file diff --git a/backend/dist/ai/ai.controller.js.map b/backend/dist/ai/ai.controller.js.map new file mode 100644 index 00000000..cb248647 --- /dev/null +++ b/backend/dist/ai/ai.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ai.controller.js","sourceRoot":"","sources":["../../src/ai/ai.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,0EAA6D;AAC7D,6CAAuD;AAEvD,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAa3C,IAAM,YAAY,GAAlB,MAAM,YAAY;IAGvB,SAAS;QACP,OAAO;YACL;gBACE,EAAE,EAAE,aAAa;gBACjB,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EAAE,oEAAoE;gBACjF,KAAK,EAAE,oBAAoB;gBAC3B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,+BAA+B;gBACxC,MAAM,EAAE,gBAAgB;aACzB;YACD;gBACE,EAAE,EAAE,eAAe;gBACnB,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EAAE,oEAAoE;gBACjF,KAAK,EAAE,oBAAoB;gBAC3B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,uDAAuD;gBAChE,MAAM,EAAE,gBAAgB;aACzB;YACD;gBACE,EAAE,EAAE,kBAAkB;gBACtB,IAAI,EAAE,mCAAmC;gBACzC,WAAW,EAAE,mGAAmG;gBAChH,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,2DAA2D;gBACpE,MAAM,EAAE,2BAA2B;aACpC;YACD;gBACE,EAAE,EAAE,iBAAiB;gBACrB,IAAI,EAAE,+BAA+B;gBACrC,WAAW,EAAE,gGAAgG;gBAC7G,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,iDAAiD;gBAC1D,MAAM,EAAE,OAAO;aAChB;YACD;gBACE,EAAE,EAAE,cAAc;gBAClB,IAAI,EAAE,wBAAwB;gBAC9B,WAAW,EAAE,2FAA2F;gBACxG,KAAK,EAAE,oCAAuB;gBAC9B,IAAI,EAAE,iBAAiB;gBACvB,OAAO,EAAE,uDAAuD;gBAChE,MAAM,EAAE,OAAO;aAChB;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AApDY,oCAAY;AAGvB;IAFC,IAAA,YAAG,EAAC,QAAQ,CAAC;IACb,IAAA,yBAAM,GAAE;;;;6CAiDR;uBAnDU,YAAY;IADxB,IAAA,mBAAU,EAAC,IAAI,CAAC;GACJ,YAAY,CAoDxB"} \ No newline at end of file diff --git a/backend/dist/ai/ai.module.d.ts b/backend/dist/ai/ai.module.d.ts new file mode 100644 index 00000000..cd4963db --- /dev/null +++ b/backend/dist/ai/ai.module.d.ts @@ -0,0 +1,2 @@ +export declare class AiModule { +} diff --git a/backend/dist/ai/ai.module.js b/backend/dist/ai/ai.module.js new file mode 100644 index 00000000..028b42d8 --- /dev/null +++ b/backend/dist/ai/ai.module.js @@ -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; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AiModule = void 0; +const common_1 = require("@nestjs/common"); +const ai_service_1 = require("./ai.service"); +const ai_controller_1 = require("./ai.controller"); +let AiModule = class AiModule { +}; +exports.AiModule = AiModule; +exports.AiModule = AiModule = __decorate([ + (0, common_1.Module)({ + controllers: [ai_controller_1.AiController], + providers: [ai_service_1.AiService], + exports: [ai_service_1.AiService], + }) +], AiModule); +//# sourceMappingURL=ai.module.js.map \ No newline at end of file diff --git a/backend/dist/ai/ai.module.js.map b/backend/dist/ai/ai.module.js.map new file mode 100644 index 00000000..fd4c8f5b --- /dev/null +++ b/backend/dist/ai/ai.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ai.module.js","sourceRoot":"","sources":["../../src/ai/ai.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAyC;AACzC,mDAA+C;AAOxC,IAAM,QAAQ,GAAd,MAAM,QAAQ;CAAG,CAAA;AAAX,4BAAQ;mBAAR,QAAQ;IALpB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,4BAAY,CAAC;QAC3B,SAAS,EAAE,CAAC,sBAAS,CAAC;QACtB,OAAO,EAAE,CAAC,sBAAS,CAAC;KACrB,CAAC;GACW,QAAQ,CAAG"} \ No newline at end of file diff --git a/backend/dist/ai/ai.service.d.ts b/backend/dist/ai/ai.service.d.ts new file mode 100644 index 00000000..bdfd702c --- /dev/null +++ b/backend/dist/ai/ai.service.d.ts @@ -0,0 +1,14 @@ +import { FlatCategory } from '../categories/categories.service'; +export declare const AI_CATEGORIZATION_MODEL = "mistral-tiny"; +export type CategorySuggestion = { + categoryId: number; + categoryName: string; + path: string; + confidence: 'high' | 'medium' | 'low'; + usedFallback: boolean; +}; +export declare class AiService { + private readonly logger; + suggestCategory(productName: string, categories: FlatCategory[]): Promise; + private fallbackToOvrigt; +} diff --git a/backend/dist/ai/ai.service.js b/backend/dist/ai/ai.service.js new file mode 100644 index 00000000..48976ac7 --- /dev/null +++ b/backend/dist/ai/ai.service.js @@ -0,0 +1,150 @@ +"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 AiService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AiService = exports.AI_CATEGORIZATION_MODEL = void 0; +const common_1 = require("@nestjs/common"); +const MISTRAL_API_URL = 'https://api.mistral.ai/v1/chat/completions'; +exports.AI_CATEGORIZATION_MODEL = 'mistral-tiny'; +const MODEL = exports.AI_CATEGORIZATION_MODEL; +let AiService = AiService_1 = class AiService { + constructor() { + this.logger = new common_1.Logger(AiService_1.name); + } + async suggestCategory(productName, categories) { + const apiKey = process.env.MISTRAL_API_KEY; + if (!apiKey) { + throw new common_1.ServiceUnavailableException('MISTRAL_API_KEY är inte konfigurerad i miljövariabler'); + } + const categoryList = categories + .map((c) => `[${c.id}] ${c.path}`) + .join('\n'); + const systemPrompt = `Du är ett kategoriseringssystem för en livsmedelsapp. Din uppgift är att hitta den mest lämpliga kategorin för en produkt. + +Tillgängliga kategorier (format: [id] Sökväg): +${categoryList} + +Regler: +1. Välj den mest specifika underkategorin som passar produkten. +2. Om ingen specifik kategori passar, välj en underkategori under "Övrigt" om möjligt. +3. Om ingen underkategori under "Övrigt" passar, välj "Övrigt" (den kategori vars sökväg är exakt "Övrigt"). +4. Du MÅSTE alltid returnera ett svar — aldrig null eller tomt. +5. Svara ENDAST med giltig JSON i detta format: { "categoryId": , "confidence": "high" | "medium" | "low" } + - "high": uppenbart matchande kategori + - "medium": trolig matchning + - "low": osäker, används fallback (Övrigt eller underkategori till Övrigt)`; + const userPrompt = `Produkt: "${productName}"`; + let raw = ''; + const MAX_RETRIES = 3; + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + const response = await fetch(MISTRAL_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: MODEL, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt }, + ], + max_tokens: 100, + temperature: 0.1, + response_format: { type: 'json_object' }, + }), + }); + if (response.status === 503 || response.status === 429) { + const err = await response.text(); + this.logger.warn(`Mistral API ${response.status} (försök ${attempt}/${MAX_RETRIES}): ${err}`); + if (attempt < MAX_RETRIES) { + await new Promise((r) => setTimeout(r, attempt * 1500)); + continue; + } + throw new common_1.ServiceUnavailableException('AI-tjänsten är tillfälligt otillgänglig, försök igen'); + } + if (!response.ok) { + const err = await response.text(); + this.logger.error(`Mistral API-fel: ${response.status} ${err}`); + throw new common_1.ServiceUnavailableException('AI-tjänsten svarade inte korrekt'); + } + const data = await response.json(); + raw = data.choices?.[0]?.message?.content ?? ''; + break; + } + catch (err) { + if (err instanceof common_1.ServiceUnavailableException) + throw err; + this.logger.error(`Mistral fetch-fel (försök ${attempt}/${MAX_RETRIES}): ${String(err)}`); + if (attempt < MAX_RETRIES) { + await new Promise((r) => setTimeout(r, attempt * 1500)); + continue; + } + throw new common_1.ServiceUnavailableException('Kunde inte nå AI-tjänsten'); + } + } + let parsed; + try { + parsed = JSON.parse(raw); + } + catch { + this.logger.warn(`AI returnerade ogiltig JSON: ${raw}`); + return this.fallbackToOvrigt(categories); + } + const validId = typeof parsed.categoryId === 'number'; + const matchedCategory = validId ? categories.find((c) => c.id === parsed.categoryId) : null; + if (!matchedCategory) { + this.logger.warn(`AI returnerade okänt categoryId ${parsed.categoryId}, använder fallback`); + return this.fallbackToOvrigt(categories); + } + const confidence = ['high', 'medium', 'low'].includes(parsed.confidence) + ? parsed.confidence + : 'medium'; + if (confidence === 'low') { + const l1Name = matchedCategory.path.split(' > ')[0]; + const l1 = categories.find((c) => c.path === l1Name); + if (l1 && l1.id !== matchedCategory.id) { + this.logger.log(`AI-guardrail: ${confidence} konfidenspoäng → remappar "${matchedCategory.path}" → L1 "${l1.path}"`); + return { + categoryId: l1.id, + categoryName: l1.name, + path: l1.path, + confidence, + usedFallback: true, + }; + } + } + return { + categoryId: matchedCategory.id, + categoryName: matchedCategory.name, + path: matchedCategory.path, + confidence, + usedFallback: confidence === 'low', + }; + } + fallbackToOvrigt(categories) { + const ovrigt = categories.find((c) => c.path === 'Övrigt'); + if (!ovrigt) { + const first = categories[0]; + return { categoryId: first.id, categoryName: first.name, path: first.path, confidence: 'low', usedFallback: true }; + } + return { + categoryId: ovrigt.id, + categoryName: ovrigt.name, + path: ovrigt.path, + confidence: 'low', + usedFallback: true, + }; + } +}; +exports.AiService = AiService; +exports.AiService = AiService = AiService_1 = __decorate([ + (0, common_1.Injectable)() +], AiService); +//# sourceMappingURL=ai.service.js.map \ No newline at end of file diff --git a/backend/dist/ai/ai.service.js.map b/backend/dist/ai/ai.service.js.map new file mode 100644 index 00000000..e76d3d8e --- /dev/null +++ b/backend/dist/ai/ai.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ai.service.js","sourceRoot":"","sources":["../../src/ai/ai.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/app.module.d.ts b/backend/dist/app.module.d.ts new file mode 100644 index 00000000..09cdb35c --- /dev/null +++ b/backend/dist/app.module.d.ts @@ -0,0 +1,2 @@ +export declare class AppModule { +} diff --git a/backend/dist/app.module.js b/backend/dist/app.module.js new file mode 100644 index 00000000..36dde1b2 --- /dev/null +++ b/backend/dist/app.module.js @@ -0,0 +1,75 @@ +"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.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const throttler_1 = require("@nestjs/throttler"); +const health_module_1 = require("./health/health.module"); +const prisma_module_1 = require("./prisma/prisma.module"); +const products_module_1 = require("./products/products.module"); +const inventory_module_1 = require("./inventory/inventory.module"); +const recipes_module_1 = require("./recipes/recipes.module"); +const quick_import_module_1 = require("./quick-import/quick-import.module"); +const pantry_module_1 = require("./pantry/pantry.module"); +const meal_plan_module_1 = require("./meal-plan/meal-plan.module"); +const receipt_import_module_1 = require("./receipt-import/receipt-import.module"); +const receipt_alias_module_1 = require("./receipt-alias/receipt-alias.module"); +const auth_module_1 = require("./auth/auth.module"); +const users_module_1 = require("./users/users.module"); +const user_products_module_1 = require("./user-products/user-products.module"); +const categories_module_1 = require("./categories/categories.module"); +const ai_module_1 = require("./ai/ai.module"); +const jwt_auth_guard_1 = require("./auth/jwt-auth.guard"); +const roles_guard_1 = require("./auth/roles.guard"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [ + throttler_1.ThrottlerModule.forRoot([ + { + name: 'default', + ttl: 60_000, + limit: 120, + }, + ]), + health_module_1.HealthModule, + prisma_module_1.PrismaModule, + products_module_1.ProductsModule, + inventory_module_1.InventoryModule, + recipes_module_1.RecipesModule, + quick_import_module_1.QuickImportModule, + pantry_module_1.PantryModule, + meal_plan_module_1.MealPlanModule, + receipt_import_module_1.ReceiptImportModule, + receipt_alias_module_1.ReceiptAliasModule, + auth_module_1.AuthModule, + users_module_1.UsersModule, + user_products_module_1.UserProductsModule, + categories_module_1.CategoriesModule, + ai_module_1.AiModule, + ], + providers: [ + { + provide: core_1.APP_GUARD, + useClass: throttler_1.ThrottlerGuard, + }, + { + provide: core_1.APP_GUARD, + useClass: jwt_auth_guard_1.JwtAuthGuard, + }, + { + provide: core_1.APP_GUARD, + useClass: roles_guard_1.RolesGuard, + }, + ], + }) +], AppModule); +//# sourceMappingURL=app.module.js.map \ No newline at end of file diff --git a/backend/dist/app.module.js.map b/backend/dist/app.module.js.map new file mode 100644 index 00000000..101c2f8c --- /dev/null +++ b/backend/dist/app.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uCAAyC;AACzC,iDAAoE;AACpE,0DAAsD;AACtD,0DAAsD;AACtD,gEAA4D;AAC5D,mEAA+D;AAC/D,6DAAyD;AACzD,4EAAuE;AACvE,0DAAsD;AACtD,mEAA8D;AAC9D,kFAA6E;AAC7E,+EAA0E;AAC1E,oDAAgD;AAChD,uDAAmD;AACnD,+EAA0E;AAC1E,sEAAkE;AAClE,8CAA0C;AAC1C,0DAAqD;AACrD,oDAAgD;AA2CzC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAxCrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,2BAAe,CAAC,OAAO,CAAC;gBACtB;oBACE,IAAI,EAAE,SAAS;oBACf,GAAG,EAAE,MAAM;oBACX,KAAK,EAAE,GAAG;iBACX;aACF,CAAC;YACF,4BAAY;YACZ,4BAAY;YACZ,gCAAc;YACd,kCAAe;YACf,8BAAa;YACb,uCAAiB;YACjB,4BAAY;YACZ,iCAAc;YACd,2CAAmB;YACnB,yCAAkB;YAClB,wBAAU;YACV,0BAAW;YACX,yCAAkB;YAClB,oCAAgB;YAChB,oBAAQ;SACT;QACD,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,0BAAc;aACzB;YACD;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,6BAAY;aACvB;YACD;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,wBAAU;aACrB;SACF;KACF,CAAC;GACW,SAAS,CAAG"} \ No newline at end of file diff --git a/backend/dist/auth/auth.controller.d.ts b/backend/dist/auth/auth.controller.d.ts new file mode 100644 index 00000000..9943590e --- /dev/null +++ b/backend/dist/auth/auth.controller.d.ts @@ -0,0 +1,21 @@ +import { AuthService } from './auth.service'; +import { RegisterDto } from './dto/register.dto'; +import { LoginDto } from './dto/login.dto'; +export declare class AuthController { + private readonly authService; + constructor(authService: AuthService); + register(dto: RegisterDto): Promise<{ + accessToken: string; + userId: number; + username: string; + role: string; + isPremium: boolean; + }>; + login(dto: LoginDto): Promise<{ + accessToken: string; + userId: number; + username: string; + role: string; + isPremium: boolean; + }>; +} diff --git a/backend/dist/auth/auth.controller.js b/backend/dist/auth/auth.controller.js new file mode 100644 index 00000000..54cc5e86 --- /dev/null +++ b/backend/dist/auth/auth.controller.js @@ -0,0 +1,57 @@ +"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.AuthController = void 0; +const common_1 = require("@nestjs/common"); +const throttler_1 = require("@nestjs/throttler"); +const auth_service_1 = require("./auth.service"); +const register_dto_1 = require("./dto/register.dto"); +const login_dto_1 = require("./dto/login.dto"); +const public_decorator_1 = require("./decorators/public.decorator"); +let AuthController = class AuthController { + constructor(authService) { + this.authService = authService; + } + register(dto) { + return this.authService.register(dto); + } + login(dto) { + return this.authService.login(dto); + } +}; +exports.AuthController = AuthController; +__decorate([ + (0, public_decorator_1.Public)(), + (0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 10 } }), + (0, common_1.Post)('register'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [register_dto_1.RegisterDto]), + __metadata("design:returntype", void 0) +], AuthController.prototype, "register", null); +__decorate([ + (0, public_decorator_1.Public)(), + (0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 10 } }), + (0, common_1.HttpCode)(common_1.HttpStatus.OK), + (0, common_1.Post)('login'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [login_dto_1.LoginDto]), + __metadata("design:returntype", void 0) +], AuthController.prototype, "login", null); +exports.AuthController = AuthController = __decorate([ + (0, common_1.Controller)('auth'), + __metadata("design:paramtypes", [auth_service_1.AuthService]) +], AuthController); +//# sourceMappingURL=auth.controller.js.map \ No newline at end of file diff --git a/backend/dist/auth/auth.controller.js.map b/backend/dist/auth/auth.controller.js.map new file mode 100644 index 00000000..429d6682 --- /dev/null +++ b/backend/dist/auth/auth.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA8E;AAC9E,iDAA6C;AAC7C,iDAA6C;AAC7C,qDAAiD;AACjD,+CAA2C;AAC3C,oEAAuD;AAGhD,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB,YAA6B,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAKzD,QAAQ,CAAS,GAAgB;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxC,CAAC;IAMD,KAAK,CAAS,GAAa;QACzB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;CACF,CAAA;AAjBY,wCAAc;AAMzB;IAHC,IAAA,yBAAM,GAAE;IACR,IAAA,oBAAQ,EAAC,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;IACjD,IAAA,aAAI,EAAC,UAAU,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAM,0BAAW;;8CAEhC;AAMD;IAJC,IAAA,yBAAM,GAAE;IACR,IAAA,oBAAQ,EAAC,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;IACjD,IAAA,iBAAQ,EAAC,mBAAU,CAAC,EAAE,CAAC;IACvB,IAAA,aAAI,EAAC,OAAO,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAM,oBAAQ;;2CAE1B;yBAhBU,cAAc;IAD1B,IAAA,mBAAU,EAAC,MAAM,CAAC;qCAEyB,0BAAW;GAD1C,cAAc,CAiB1B"} \ No newline at end of file diff --git a/backend/dist/auth/auth.module.d.ts b/backend/dist/auth/auth.module.d.ts new file mode 100644 index 00000000..3f7dba9e --- /dev/null +++ b/backend/dist/auth/auth.module.d.ts @@ -0,0 +1,2 @@ +export declare class AuthModule { +} diff --git a/backend/dist/auth/auth.module.js b/backend/dist/auth/auth.module.js new file mode 100644 index 00000000..4c449309 --- /dev/null +++ b/backend/dist/auth/auth.module.js @@ -0,0 +1,40 @@ +"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.AuthModule = void 0; +const common_1 = require("@nestjs/common"); +const jwt_1 = require("@nestjs/jwt"); +const passport_1 = require("@nestjs/passport"); +const auth_controller_1 = require("./auth.controller"); +const auth_service_1 = require("./auth.service"); +const jwt_strategy_1 = require("./jwt.strategy"); +const users_module_1 = require("../users/users.module"); +let AuthModule = class AuthModule { +}; +exports.AuthModule = AuthModule; +exports.AuthModule = AuthModule = __decorate([ + (0, common_1.Module)({ + imports: [ + users_module_1.UsersModule, + passport_1.PassportModule, + jwt_1.JwtModule.register({ + secret: (() => { + const secret = process.env.JWT_SECRET; + if (!secret) + throw new Error('JWT_SECRET saknas i miljövariabler'); + return secret; + })(), + signOptions: { expiresIn: '7d' }, + }), + ], + controllers: [auth_controller_1.AuthController], + providers: [auth_service_1.AuthService, jwt_strategy_1.JwtStrategy], + exports: [auth_service_1.AuthService], + }) +], AuthModule); +//# sourceMappingURL=auth.module.js.map \ No newline at end of file diff --git a/backend/dist/auth/auth.module.js.map b/backend/dist/auth/auth.module.js.map new file mode 100644 index 00000000..4fb3f236 --- /dev/null +++ b/backend/dist/auth/auth.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qCAAwC;AACxC,+CAAkD;AAClD,uDAAmD;AACnD,iDAA6C;AAC7C,iDAA6C;AAC7C,wDAAoD;AAmB7C,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAjBtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,0BAAW;YACX,yBAAc;YACd,eAAS,CAAC,QAAQ,CAAC;gBACjB,MAAM,EAAE,CAAC,GAAG,EAAE;oBACZ,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;oBACtC,IAAI,CAAC,MAAM;wBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;oBACnE,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC,EAAE;gBACJ,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;aACjC,CAAC;SACH;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,0BAAW,CAAC;QACrC,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"} \ No newline at end of file diff --git a/backend/dist/auth/auth.service.d.ts b/backend/dist/auth/auth.service.d.ts new file mode 100644 index 00000000..7eea5d08 --- /dev/null +++ b/backend/dist/auth/auth.service.d.ts @@ -0,0 +1,24 @@ +import { JwtService } from '@nestjs/jwt'; +import { UsersService } from '../users/users.service'; +import { RegisterDto } from './dto/register.dto'; +import { LoginDto } from './dto/login.dto'; +export declare class AuthService { + private readonly usersService; + private readonly jwtService; + constructor(usersService: UsersService, jwtService: JwtService); + register(dto: RegisterDto): Promise<{ + accessToken: string; + userId: number; + username: string; + role: string; + isPremium: boolean; + }>; + login(dto: LoginDto): Promise<{ + accessToken: string; + userId: number; + username: string; + role: string; + isPremium: boolean; + }>; + private issueToken; +} diff --git a/backend/dist/auth/auth.service.js b/backend/dist/auth/auth.service.js new file mode 100644 index 00000000..09f90e75 --- /dev/null +++ b/backend/dist/auth/auth.service.js @@ -0,0 +1,60 @@ +"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.AuthService = void 0; +const common_1 = require("@nestjs/common"); +const jwt_1 = require("@nestjs/jwt"); +const bcrypt = require("bcryptjs"); +const users_service_1 = require("../users/users.service"); +let AuthService = class AuthService { + constructor(usersService, jwtService) { + this.usersService = usersService; + this.jwtService = jwtService; + } + async register(dto) { + const existing = await this.usersService.findByUsername(dto.username); + if (existing) + throw new common_1.ConflictException('Användarnamnet är redan taget'); + const passwordHash = await bcrypt.hash(dto.password, 12); + const user = await this.usersService.create({ + username: dto.username, + email: dto.email, + passwordHash, + }); + return this.issueToken(user.id, user.username, user.role, user.isPremium); + } + async login(dto) { + const user = await this.usersService.findByUsername(dto.username); + if (!user) + throw new common_1.UnauthorizedException('Felaktigt användarnamn eller lösenord'); + const valid = await bcrypt.compare(dto.password, user.passwordHash); + if (!valid) + throw new common_1.UnauthorizedException('Felaktigt användarnamn eller lösenord'); + return this.issueToken(user.id, user.username, user.role, user.isPremium); + } + issueToken(userId, username, role, isPremium) { + const payload = { sub: userId, username, role, isPremium }; + return { + accessToken: this.jwtService.sign(payload), + userId, + username, + role, + isPremium, + }; + } +}; +exports.AuthService = AuthService; +exports.AuthService = AuthService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [users_service_1.UsersService, + jwt_1.JwtService]) +], AuthService); +//# sourceMappingURL=auth.service.js.map \ No newline at end of file diff --git a/backend/dist/auth/auth.service.js.map b/backend/dist/auth/auth.service.js.map new file mode 100644 index 00000000..71c17364 --- /dev/null +++ b/backend/dist/auth/auth.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/auth/auth.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAIwB;AACxB,qCAAyC;AACzC,mCAAmC;AACnC,0DAAsD;AAK/C,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB,YACmB,YAA0B,EAC1B,UAAsB;QADtB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAAY;IACtC,CAAC;IAEJ,KAAK,CAAC,QAAQ,CAAC,GAAgB;QAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtE,IAAI,QAAQ;YAAE,MAAM,IAAI,0BAAiB,CAAC,+BAA+B,CAAC,CAAC;QAE3E,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAC1C,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,YAAY;SACb,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAa;QACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,8BAAqB,CAAC,uCAAuC,CAAC,CAAC;QAEpF,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACpE,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,8BAAqB,CAAC,uCAAuC,CAAC,CAAC;QAErF,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5E,CAAC;IAEO,UAAU,CAAC,MAAc,EAAE,QAAgB,EAAE,IAAY,EAAE,SAAkB;QACnF,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC3D,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;YAC1C,MAAM;YACN,QAAQ;YACR,IAAI;YACJ,SAAS;SACV,CAAC;IACJ,CAAC;CACF,CAAA;AAxCY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAGsB,4BAAY;QACd,gBAAU;GAH9B,WAAW,CAwCvB"} \ No newline at end of file diff --git a/backend/dist/auth/decorators/current-user.decorator.d.ts b/backend/dist/auth/decorators/current-user.decorator.d.ts new file mode 100644 index 00000000..78b7a5ff --- /dev/null +++ b/backend/dist/auth/decorators/current-user.decorator.d.ts @@ -0,0 +1 @@ +export declare const CurrentUser: (...dataOrPipes: unknown[]) => ParameterDecorator; diff --git a/backend/dist/auth/decorators/current-user.decorator.js b/backend/dist/auth/decorators/current-user.decorator.js new file mode 100644 index 00000000..35d1f321 --- /dev/null +++ b/backend/dist/auth/decorators/current-user.decorator.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CurrentUser = void 0; +const common_1 = require("@nestjs/common"); +exports.CurrentUser = (0, common_1.createParamDecorator)((_data, ctx) => { + const request = ctx.switchToHttp().getRequest(); + return request.user; +}); +//# sourceMappingURL=current-user.decorator.js.map \ No newline at end of file diff --git a/backend/dist/auth/decorators/current-user.decorator.js.map b/backend/dist/auth/decorators/current-user.decorator.js.map new file mode 100644 index 00000000..dbcfa080 --- /dev/null +++ b/backend/dist/auth/decorators/current-user.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"current-user.decorator.js","sourceRoot":"","sources":["../../../src/auth/decorators/current-user.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAAwE;AAE3D,QAAA,WAAW,GAAG,IAAA,6BAAoB,EAC7C,CAAC,KAAc,EAAE,GAAqB,EAAE,EAAE;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;IAChD,OAAO,OAAO,CAAC,IAA4C,CAAC;AAC9D,CAAC,CACF,CAAC"} \ No newline at end of file diff --git a/backend/dist/auth/decorators/public.decorator.d.ts b/backend/dist/auth/decorators/public.decorator.d.ts new file mode 100644 index 00000000..24db7b0e --- /dev/null +++ b/backend/dist/auth/decorators/public.decorator.d.ts @@ -0,0 +1,2 @@ +export declare const IS_PUBLIC_KEY = "isPublic"; +export declare const Public: () => import("@nestjs/common").CustomDecorator; diff --git a/backend/dist/auth/decorators/public.decorator.js b/backend/dist/auth/decorators/public.decorator.js new file mode 100644 index 00000000..6ff126d5 --- /dev/null +++ b/backend/dist/auth/decorators/public.decorator.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Public = exports.IS_PUBLIC_KEY = void 0; +const common_1 = require("@nestjs/common"); +exports.IS_PUBLIC_KEY = 'isPublic'; +const Public = () => (0, common_1.SetMetadata)(exports.IS_PUBLIC_KEY, true); +exports.Public = Public; +//# sourceMappingURL=public.decorator.js.map \ No newline at end of file diff --git a/backend/dist/auth/decorators/public.decorator.js.map b/backend/dist/auth/decorators/public.decorator.js.map new file mode 100644 index 00000000..d04ca827 --- /dev/null +++ b/backend/dist/auth/decorators/public.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"public.decorator.js","sourceRoot":"","sources":["../../../src/auth/decorators/public.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAEhC,QAAA,aAAa,GAAG,UAAU,CAAC;AACjC,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,IAAA,oBAAW,EAAC,qBAAa,EAAE,IAAI,CAAC,CAAC;AAAhD,QAAA,MAAM,UAA0C"} \ No newline at end of file diff --git a/backend/dist/auth/decorators/roles.decorator.d.ts b/backend/dist/auth/decorators/roles.decorator.d.ts new file mode 100644 index 00000000..bd39810e --- /dev/null +++ b/backend/dist/auth/decorators/roles.decorator.d.ts @@ -0,0 +1,2 @@ +export declare const ROLES_KEY = "roles"; +export declare const Roles: (...roles: string[]) => import("@nestjs/common").CustomDecorator; diff --git a/backend/dist/auth/decorators/roles.decorator.js b/backend/dist/auth/decorators/roles.decorator.js new file mode 100644 index 00000000..126533b0 --- /dev/null +++ b/backend/dist/auth/decorators/roles.decorator.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Roles = exports.ROLES_KEY = void 0; +const common_1 = require("@nestjs/common"); +exports.ROLES_KEY = 'roles'; +const Roles = (...roles) => (0, common_1.SetMetadata)(exports.ROLES_KEY, roles); +exports.Roles = Roles; +//# sourceMappingURL=roles.decorator.js.map \ No newline at end of file diff --git a/backend/dist/auth/decorators/roles.decorator.js.map b/backend/dist/auth/decorators/roles.decorator.js.map new file mode 100644 index 00000000..4086ba68 --- /dev/null +++ b/backend/dist/auth/decorators/roles.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"roles.decorator.js","sourceRoot":"","sources":["../../../src/auth/decorators/roles.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAEhC,QAAA,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,IAAA,oBAAW,EAAC,iBAAS,EAAE,KAAK,CAAC,CAAC;AAA9D,QAAA,KAAK,SAAyD"} \ No newline at end of file diff --git a/backend/dist/auth/dto/login.dto.d.ts b/backend/dist/auth/dto/login.dto.d.ts new file mode 100644 index 00000000..ecc153ad --- /dev/null +++ b/backend/dist/auth/dto/login.dto.d.ts @@ -0,0 +1,4 @@ +export declare class LoginDto { + username: string; + password: string; +} diff --git a/backend/dist/auth/dto/login.dto.js b/backend/dist/auth/dto/login.dto.js new file mode 100644 index 00000000..121e8dbb --- /dev/null +++ b/backend/dist/auth/dto/login.dto.js @@ -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.LoginDto = void 0; +const class_validator_1 = require("class-validator"); +class LoginDto { +} +exports.LoginDto = LoginDto; +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], LoginDto.prototype, "username", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], LoginDto.prototype, "password", void 0); +//# sourceMappingURL=login.dto.js.map \ No newline at end of file diff --git a/backend/dist/auth/dto/login.dto.js.map b/backend/dist/auth/dto/login.dto.js.map new file mode 100644 index 00000000..3bd5679d --- /dev/null +++ b/backend/dist/auth/dto/login.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"login.dto.js","sourceRoot":"","sources":["../../../src/auth/dto/login.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA2C;AAE3C,MAAa,QAAQ;CAMpB;AAND,4BAMC;AAJC;IADC,IAAA,0BAAQ,GAAE;;0CACM;AAGjB;IADC,IAAA,0BAAQ,GAAE;;0CACM"} \ No newline at end of file diff --git a/backend/dist/auth/dto/register.dto.d.ts b/backend/dist/auth/dto/register.dto.d.ts new file mode 100644 index 00000000..3e85a789 --- /dev/null +++ b/backend/dist/auth/dto/register.dto.d.ts @@ -0,0 +1,5 @@ +export declare class RegisterDto { + username: string; + email: string; + password: string; +} diff --git a/backend/dist/auth/dto/register.dto.js b/backend/dist/auth/dto/register.dto.js new file mode 100644 index 00000000..af99a11a --- /dev/null +++ b/backend/dist/auth/dto/register.dto.js @@ -0,0 +1,30 @@ +"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.RegisterDto = void 0; +const class_validator_1 = require("class-validator"); +class RegisterDto { +} +exports.RegisterDto = RegisterDto; +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], RegisterDto.prototype, "username", void 0); +__decorate([ + (0, class_validator_1.IsEmail)(), + __metadata("design:type", String) +], RegisterDto.prototype, "email", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(8), + __metadata("design:type", String) +], RegisterDto.prototype, "password", void 0); +//# sourceMappingURL=register.dto.js.map \ No newline at end of file diff --git a/backend/dist/auth/dto/register.dto.js.map b/backend/dist/auth/dto/register.dto.js.map new file mode 100644 index 00000000..b45ef3ec --- /dev/null +++ b/backend/dist/auth/dto/register.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"register.dto.js","sourceRoot":"","sources":["../../../src/auth/dto/register.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA+D;AAE/D,MAAa,WAAW;CAUvB;AAVD,kCAUC;AARC;IADC,IAAA,0BAAQ,GAAE;;6CACM;AAGjB;IADC,IAAA,yBAAO,GAAE;;0CACI;AAId;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;6CACI"} \ No newline at end of file diff --git a/backend/dist/auth/jwt-auth.guard.d.ts b/backend/dist/auth/jwt-auth.guard.d.ts new file mode 100644 index 00000000..f65906f9 --- /dev/null +++ b/backend/dist/auth/jwt-auth.guard.d.ts @@ -0,0 +1,12 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { Reflector } from '@nestjs/core'; +declare const JwtAuthGuard_base: import("@nestjs/passport").Type; +export declare class JwtAuthGuard extends JwtAuthGuard_base { + private reflector; + private readonly logger; + constructor(reflector: Reflector); + canActivate(context: ExecutionContext): boolean | Promise | Observable; + handleRequest(err: any, user: TUser, info: any): TUser; +} +export {}; diff --git a/backend/dist/auth/jwt-auth.guard.js b/backend/dist/auth/jwt-auth.guard.js new file mode 100644 index 00000000..de7260af --- /dev/null +++ b/backend/dist/auth/jwt-auth.guard.js @@ -0,0 +1,48 @@ +"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 JwtAuthGuard_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JwtAuthGuard = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const passport_1 = require("@nestjs/passport"); +const public_decorator_1 = require("./decorators/public.decorator"); +let JwtAuthGuard = JwtAuthGuard_1 = class JwtAuthGuard extends (0, passport_1.AuthGuard)('jwt') { + constructor(reflector) { + super(); + this.reflector = reflector; + this.logger = new common_1.Logger(JwtAuthGuard_1.name); + } + canActivate(context) { + const isPublic = this.reflector.getAllAndOverride(public_decorator_1.IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) + return true; + return super.canActivate(context); + } + handleRequest(err, user, info) { + if (err || !user) { + throw err || new common_1.UnauthorizedException(); + } + if (user) { + this.logger.log(`Authenticated user ID: ${user.userId}`); + } + return user; + } +}; +exports.JwtAuthGuard = JwtAuthGuard; +exports.JwtAuthGuard = JwtAuthGuard = JwtAuthGuard_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [core_1.Reflector]) +], JwtAuthGuard); +//# sourceMappingURL=jwt-auth.guard.js.map \ No newline at end of file diff --git a/backend/dist/auth/jwt-auth.guard.js.map b/backend/dist/auth/jwt-auth.guard.js.map new file mode 100644 index 00000000..8b501d42 --- /dev/null +++ b/backend/dist/auth/jwt-auth.guard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../src/auth/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA0G;AAE1G,uCAAyC;AACzC,+CAA6C;AAC7C,oEAA8D;AAGvD,IAAM,YAAY,oBAAlB,MAAM,YAAa,SAAQ,IAAA,oBAAS,EAAC,KAAK,CAAC;IAGhD,YAAoB,SAAoB;QACtC,KAAK,EAAE,CAAC;QADU,cAAS,GAAT,SAAS,CAAW;QAFvB,WAAM,GAAG,IAAI,eAAM,CAAC,cAAY,CAAC,IAAI,CAAC,CAAC;IAIxD,CAAC;IAED,WAAW,CAAC,OAAyB;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAU,gCAAa,EAAE;YACxE,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,CAAC;QACH,IAAI,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1B,OAAO,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAGD,aAAa,CAAc,GAAQ,EAAE,IAAW,EAAE,IAAS;QACzD,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,IAAI,IAAI,8BAAqB,EAAE,CAAC;QAC3C,CAAC;QACD,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA2B,IAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA1BY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAIoB,gBAAS;GAH7B,YAAY,CA0BxB"} \ No newline at end of file diff --git a/backend/dist/auth/jwt.strategy.d.ts b/backend/dist/auth/jwt.strategy.d.ts new file mode 100644 index 00000000..d2cfcbb5 --- /dev/null +++ b/backend/dist/auth/jwt.strategy.d.ts @@ -0,0 +1,21 @@ +import { Strategy } from 'passport-jwt'; +declare const JwtStrategy_base: new (...args: any[]) => Strategy; +export declare class JwtStrategy extends JwtStrategy_base { + private readonly logger; + constructor(); + validate(payload: { + sub?: number; + id?: number; + userId?: number; + username?: string; + role?: string; + isPremium?: boolean; + }): Promise<{ + id: number | undefined; + userId: number | undefined; + username: string | undefined; + role: string; + isPremium: boolean; + }>; +} +export {}; diff --git a/backend/dist/auth/jwt.strategy.js b/backend/dist/auth/jwt.strategy.js new file mode 100644 index 00000000..42b89f1f --- /dev/null +++ b/backend/dist/auth/jwt.strategy.js @@ -0,0 +1,43 @@ +"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 JwtStrategy_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JwtStrategy = void 0; +const common_1 = require("@nestjs/common"); +const passport_1 = require("@nestjs/passport"); +const passport_jwt_1 = require("passport-jwt"); +let JwtStrategy = JwtStrategy_1 = class JwtStrategy extends (0, passport_1.PassportStrategy)(passport_jwt_1.Strategy) { + constructor() { + super({ + jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_SECRET, + }); + this.logger = new common_1.Logger(JwtStrategy_1.name); + } + async validate(payload) { + const resolvedUserId = payload.sub ?? payload.id ?? payload.userId; + this.logger.log(`Validating token for user ID: ${resolvedUserId}, Username: ${payload.username}`); + return { + id: resolvedUserId, + userId: resolvedUserId, + username: payload.username, + role: payload.role ?? 'user', + isPremium: payload.isPremium ?? false, + }; + } +}; +exports.JwtStrategy = JwtStrategy; +exports.JwtStrategy = JwtStrategy = JwtStrategy_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", []) +], JwtStrategy); +//# sourceMappingURL=jwt.strategy.js.map \ No newline at end of file diff --git a/backend/dist/auth/jwt.strategy.js.map b/backend/dist/auth/jwt.strategy.js.map new file mode 100644 index 00000000..21c0a1d4 --- /dev/null +++ b/backend/dist/auth/jwt.strategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,+CAAoD;AACpD,+CAAoD;AAG7C,IAAM,WAAW,mBAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IAGzD;QACE,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU;SACpC,CAAC,CAAC;QAPY,WAAM,GAAG,IAAI,eAAM,CAAC,aAAW,CAAC,IAAI,CAAC,CAAC;IAQvD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAA8G;QAC3H,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iCAAiC,cAAc,eAAe,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClG,OAAO;YACL,EAAE,EAAE,cAAc;YAClB,MAAM,EAAE,cAAc;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,MAAM;YAC5B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK;SACtC,CAAC;IACJ,CAAC;CACF,CAAA;AAtBY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;;GACA,WAAW,CAsBvB"} \ No newline at end of file diff --git a/backend/dist/auth/premium-or-admin.guard.d.ts b/backend/dist/auth/premium-or-admin.guard.d.ts new file mode 100644 index 00000000..c1b45a08 --- /dev/null +++ b/backend/dist/auth/premium-or-admin.guard.d.ts @@ -0,0 +1,4 @@ +import { CanActivate, ExecutionContext } from '@nestjs/common'; +export declare class PremiumOrAdminGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean; +} diff --git a/backend/dist/auth/premium-or-admin.guard.js b/backend/dist/auth/premium-or-admin.guard.js new file mode 100644 index 00000000..7e381304 --- /dev/null +++ b/backend/dist/auth/premium-or-admin.guard.js @@ -0,0 +1,24 @@ +"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.PremiumOrAdminGuard = void 0; +const common_1 = require("@nestjs/common"); +let PremiumOrAdminGuard = class PremiumOrAdminGuard { + canActivate(context) { + const { user } = context.switchToHttp().getRequest(); + if (user?.role === 'admin' || user?.isPremium === true) { + return true; + } + throw new common_1.ForbiddenException('Denna funktion kräver premiumkonto eller admin-behörighet.'); + } +}; +exports.PremiumOrAdminGuard = PremiumOrAdminGuard; +exports.PremiumOrAdminGuard = PremiumOrAdminGuard = __decorate([ + (0, common_1.Injectable)() +], PremiumOrAdminGuard); +//# sourceMappingURL=premium-or-admin.guard.js.map \ No newline at end of file diff --git a/backend/dist/auth/premium-or-admin.guard.js.map b/backend/dist/auth/premium-or-admin.guard.js.map new file mode 100644 index 00000000..aade3879 --- /dev/null +++ b/backend/dist/auth/premium-or-admin.guard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"premium-or-admin.guard.js","sourceRoot":"","sources":["../../src/auth/premium-or-admin.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA+F;AAOxF,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,WAAW,CAAC,OAAyB;QACnC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAErD,IAAI,IAAI,EAAE,IAAI,KAAK,OAAO,IAAI,IAAI,EAAE,SAAS,KAAK,IAAI,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,2BAAkB,CAAC,4DAA4D,CAAC,CAAC;IAC7F,CAAC;CACF,CAAA;AAVY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;GACA,mBAAmB,CAU/B"} \ No newline at end of file diff --git a/backend/dist/auth/roles.guard.d.ts b/backend/dist/auth/roles.guard.d.ts new file mode 100644 index 00000000..3e9645ac --- /dev/null +++ b/backend/dist/auth/roles.guard.d.ts @@ -0,0 +1,7 @@ +import { CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +export declare class RolesGuard implements CanActivate { + private reflector; + constructor(reflector: Reflector); + canActivate(context: ExecutionContext): boolean; +} diff --git a/backend/dist/auth/roles.guard.js b/backend/dist/auth/roles.guard.js new file mode 100644 index 00000000..95ad8e4a --- /dev/null +++ b/backend/dist/auth/roles.guard.js @@ -0,0 +1,39 @@ +"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.RolesGuard = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const roles_decorator_1 = require("./decorators/roles.decorator"); +let RolesGuard = class RolesGuard { + constructor(reflector) { + this.reflector = reflector; + } + canActivate(context) { + const requiredRoles = this.reflector.getAllAndOverride(roles_decorator_1.ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles || requiredRoles.length === 0) + return true; + const { user } = context.switchToHttp().getRequest(); + if (!user?.role || !requiredRoles.includes(user.role)) { + throw new common_1.ForbiddenException('Åtkomst nekad — admin-behörighet krävs'); + } + return true; + } +}; +exports.RolesGuard = RolesGuard; +exports.RolesGuard = RolesGuard = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [core_1.Reflector]) +], RolesGuard); +//# sourceMappingURL=roles.guard.js.map \ No newline at end of file diff --git a/backend/dist/auth/roles.guard.js.map b/backend/dist/auth/roles.guard.js.map new file mode 100644 index 00000000..81574354 --- /dev/null +++ b/backend/dist/auth/roles.guard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"roles.guard.js","sourceRoot":"","sources":["../../src/auth/roles.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+F;AAC/F,uCAAyC;AACzC,kEAAyD;AAGlD,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,YAAoB,SAAoB;QAApB,cAAS,GAAT,SAAS,CAAW;IAAG,CAAC;IAE5C,WAAW,CAAC,OAAyB;QACnC,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAW,2BAAS,EAAE;YAC1E,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,CAAC;QAGH,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAE9D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACrD,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,2BAAkB,CAAC,wCAAwC,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAlBY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;qCAEoB,gBAAS;GAD7B,UAAU,CAkBtB"} \ No newline at end of file diff --git a/backend/dist/categories/categories.controller.d.ts b/backend/dist/categories/categories.controller.d.ts new file mode 100644 index 00000000..69161c06 --- /dev/null +++ b/backend/dist/categories/categories.controller.d.ts @@ -0,0 +1,11 @@ +import { CategoriesService } from './categories.service'; +export declare class CategoriesController { + private readonly categoriesService; + constructor(categoriesService: CategoriesService); + findAll(): Promise<{ + name: string; + id: number; + parentId: number | null; + }[]>; + findTree(): Promise; +} diff --git a/backend/dist/categories/categories.controller.js b/backend/dist/categories/categories.controller.js new file mode 100644 index 00000000..6931f3ce --- /dev/null +++ b/backend/dist/categories/categories.controller.js @@ -0,0 +1,46 @@ +"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.CategoriesController = void 0; +const common_1 = require("@nestjs/common"); +const categories_service_1 = require("./categories.service"); +const public_decorator_1 = require("../auth/decorators/public.decorator"); +let CategoriesController = class CategoriesController { + constructor(categoriesService) { + this.categoriesService = categoriesService; + } + findAll() { + return this.categoriesService.findAll(); + } + findTree() { + return this.categoriesService.findTree(); + } +}; +exports.CategoriesController = CategoriesController; +__decorate([ + (0, public_decorator_1.Public)(), + (0, common_1.Get)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], CategoriesController.prototype, "findAll", null); +__decorate([ + (0, public_decorator_1.Public)(), + (0, common_1.Get)('tree'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], CategoriesController.prototype, "findTree", null); +exports.CategoriesController = CategoriesController = __decorate([ + (0, common_1.Controller)('categories'), + __metadata("design:paramtypes", [categories_service_1.CategoriesService]) +], CategoriesController); +//# sourceMappingURL=categories.controller.js.map \ No newline at end of file diff --git a/backend/dist/categories/categories.controller.js.map b/backend/dist/categories/categories.controller.js.map new file mode 100644 index 00000000..952837b7 --- /dev/null +++ b/backend/dist/categories/categories.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"categories.controller.js","sourceRoot":"","sources":["../../src/categories/categories.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,6DAAyD;AACzD,0EAA6D;AAGtD,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAC/B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAIrE,OAAO;QACL,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;IAC1C,CAAC;IAID,QAAQ;QACN,OAAO,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;IAC3C,CAAC;CACF,CAAA;AAdY,oDAAoB;AAK/B;IAFC,IAAA,yBAAM,GAAE;IACR,IAAA,YAAG,GAAE;;;;mDAGL;AAID;IAFC,IAAA,yBAAM,GAAE;IACR,IAAA,YAAG,EAAC,MAAM,CAAC;;;;oDAGX;+BAbU,oBAAoB;IADhC,IAAA,mBAAU,EAAC,YAAY,CAAC;qCAEyB,sCAAiB;GADtD,oBAAoB,CAchC"} \ No newline at end of file diff --git a/backend/dist/categories/categories.module.d.ts b/backend/dist/categories/categories.module.d.ts new file mode 100644 index 00000000..a91213cf --- /dev/null +++ b/backend/dist/categories/categories.module.d.ts @@ -0,0 +1,2 @@ +export declare class CategoriesModule { +} diff --git a/backend/dist/categories/categories.module.js b/backend/dist/categories/categories.module.js new file mode 100644 index 00000000..eefbf6c6 --- /dev/null +++ b/backend/dist/categories/categories.module.js @@ -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.CategoriesModule = void 0; +const common_1 = require("@nestjs/common"); +const categories_service_1 = require("./categories.service"); +const categories_controller_1 = require("./categories.controller"); +const prisma_module_1 = require("../prisma/prisma.module"); +let CategoriesModule = class CategoriesModule { +}; +exports.CategoriesModule = CategoriesModule; +exports.CategoriesModule = CategoriesModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + providers: [categories_service_1.CategoriesService], + controllers: [categories_controller_1.CategoriesController], + exports: [categories_service_1.CategoriesService], + }) +], CategoriesModule); +//# sourceMappingURL=categories.module.js.map \ No newline at end of file diff --git a/backend/dist/categories/categories.module.js.map b/backend/dist/categories/categories.module.js.map new file mode 100644 index 00000000..eaca1830 --- /dev/null +++ b/backend/dist/categories/categories.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"categories.module.js","sourceRoot":"","sources":["../../src/categories/categories.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6DAAyD;AACzD,mEAA+D;AAC/D,2DAAuD;AAQhD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAAG,CAAA;AAAnB,4CAAgB;2BAAhB,gBAAgB;IAN5B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,SAAS,EAAE,CAAC,sCAAiB,CAAC;QAC9B,WAAW,EAAE,CAAC,4CAAoB,CAAC;QACnC,OAAO,EAAE,CAAC,sCAAiB,CAAC;KAC7B,CAAC;GACW,gBAAgB,CAAG"} \ No newline at end of file diff --git a/backend/dist/categories/categories.service.d.ts b/backend/dist/categories/categories.service.d.ts new file mode 100644 index 00000000..7188b859 --- /dev/null +++ b/backend/dist/categories/categories.service.d.ts @@ -0,0 +1,23 @@ +import { PrismaService } from '../prisma/prisma.service'; +export type CategoryNode = { + id: number; + name: string; + parentId: number | null; + children: CategoryNode[]; +}; +export type FlatCategory = { + id: number; + name: string; + path: string; +}; +export declare class CategoriesService { + private readonly prisma; + constructor(prisma: PrismaService); + findAll(): Promise<{ + name: string; + id: number; + parentId: number | null; + }[]>; + findTree(): Promise; + findFlattened(): Promise; +} diff --git a/backend/dist/categories/categories.service.js b/backend/dist/categories/categories.service.js new file mode 100644 index 00000000..7e55bb79 --- /dev/null +++ b/backend/dist/categories/categories.service.js @@ -0,0 +1,67 @@ +"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.CategoriesService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +let CategoriesService = class CategoriesService { + constructor(prisma) { + this.prisma = prisma; + } + async findAll() { + return this.prisma.category.findMany({ orderBy: { name: 'asc' } }); + } + async findTree() { + const all = await this.prisma.category.findMany({ orderBy: { name: 'asc' } }); + const map = new Map(); + all.forEach((c) => map.set(c.id, { ...c, children: [] })); + const roots = []; + map.forEach((node) => { + if (node.parentId === null) { + roots.push(node); + } + else { + map.get(node.parentId)?.children.push(node); + } + }); + return roots; + } + async findFlattened() { + const all = await this.prisma.category.findMany({ orderBy: { name: 'asc' } }); + const byId = new Map(); + all.forEach((c) => byId.set(c.id, c)); + const pathCache = new Map(); + const buildPath = (id) => { + const cached = pathCache.get(id); + if (cached) + return cached; + const current = byId.get(id); + if (!current) + return ''; + const path = current.parentId == null + ? current.name + : `${buildPath(current.parentId)} > ${current.name}`; + pathCache.set(id, path); + return path; + }; + return all.map((c) => ({ + id: c.id, + name: c.name, + path: buildPath(c.id), + })); + } +}; +exports.CategoriesService = CategoriesService; +exports.CategoriesService = CategoriesService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], CategoriesService); +//# sourceMappingURL=categories.service.js.map \ No newline at end of file diff --git a/backend/dist/categories/categories.service.js.map b/backend/dist/categories/categories.service.js.map new file mode 100644 index 00000000..2c1c62de --- /dev/null +++ b/backend/dist/categories/categories.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"categories.service.js","sourceRoot":"","sources":["../../src/categories/categories.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/common/filters/global-exception.filter.d.ts b/backend/dist/common/filters/global-exception.filter.d.ts new file mode 100644 index 00000000..ffe53af2 --- /dev/null +++ b/backend/dist/common/filters/global-exception.filter.d.ts @@ -0,0 +1,5 @@ +import { ExceptionFilter, ArgumentsHost } from '@nestjs/common'; +export declare class GlobalExceptionFilter implements ExceptionFilter { + private readonly logger; + catch(exception: any, host: ArgumentsHost): void; +} diff --git a/backend/dist/common/filters/global-exception.filter.js b/backend/dist/common/filters/global-exception.filter.js new file mode 100644 index 00000000..c0a03664 --- /dev/null +++ b/backend/dist/common/filters/global-exception.filter.js @@ -0,0 +1,74 @@ +"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 GlobalExceptionFilter_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GlobalExceptionFilter = void 0; +const common_1 = require("@nestjs/common"); +let GlobalExceptionFilter = GlobalExceptionFilter_1 = class GlobalExceptionFilter { + constructor() { + this.logger = new common_1.Logger(GlobalExceptionFilter_1.name); + } + catch(exception, host) { + if (!host || host.getType() !== 'http') { + this.logger.error(`Non-HTTP exception caught: ${exception?.message ?? exception}`); + return; + } + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const path = request.url; + let statusCode = common_1.HttpStatus.INTERNAL_SERVER_ERROR; + let message = 'Ett internt serverfel inträffade.'; + if (exception instanceof common_1.HttpException) { + statusCode = exception.getStatus(); + const exceptionResponse = exception.getResponse(); + if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) { + message = exceptionResponse.message || message; + } + else if (typeof exceptionResponse === 'string') { + message = exceptionResponse; + } + } + else if (exception instanceof Error) { + message = exception.message || message; + statusCode = common_1.HttpStatus.BAD_REQUEST; + this.logger.error(`Error: ${exception.message}`, exception.stack); + } + if (message.includes('Unknown unit')) { + message = 'Okänd enhet. Kontrollera enheten och försök igen.'; + } + else if (message.includes('Cannot convert between incompatible unit types')) { + message = 'Kan inte konvertera mellan dessa enhetstyper.'; + } + else if (message.includes('Inventory item with id') && message.includes('not found')) { + message = 'Hemmavarorna hittades inte.'; + } + else if (message.includes('Product with id') && message.includes('not found')) { + message = 'Produkten hittades inte.'; + } + else if (message.includes('Recipe with id') && message.includes('not found')) { + message = 'Receptet hittades inte.'; + } + else if (message.includes('sourceProductId och targetProductId kan inte vara samma')) { + message = 'Du kan inte slå samman en produkt med sig själv.'; + } + const errorResponse = { + statusCode, + message, + timestamp: new Date().toISOString(), + path, + }; + this.logger.warn(`${request.method} ${path} - Status: ${statusCode}, Message: ${message}`); + response.status(statusCode).json(errorResponse); + } +}; +exports.GlobalExceptionFilter = GlobalExceptionFilter; +exports.GlobalExceptionFilter = GlobalExceptionFilter = GlobalExceptionFilter_1 = __decorate([ + (0, common_1.Catch)() +], GlobalExceptionFilter); +//# sourceMappingURL=global-exception.filter.js.map \ No newline at end of file diff --git a/backend/dist/common/filters/global-exception.filter.js.map b/backend/dist/common/filters/global-exception.filter.js.map new file mode 100644 index 00000000..fecc7db5 --- /dev/null +++ b/backend/dist/common/filters/global-exception.filter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"global-exception.filter.js","sourceRoot":"","sources":["../../../src/common/filters/global-exception.filter.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/common/utils/download-image.d.ts b/backend/dist/common/utils/download-image.d.ts new file mode 100644 index 00000000..b8e0091d --- /dev/null +++ b/backend/dist/common/utils/download-image.d.ts @@ -0,0 +1 @@ +export declare function downloadAndOptimizeImage(sourceUrl: string, destDir: string): Promise; diff --git a/backend/dist/common/utils/download-image.js b/backend/dist/common/utils/download-image.js new file mode 100644 index 00000000..8cf35875 --- /dev/null +++ b/backend/dist/common/utils/download-image.js @@ -0,0 +1,64 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.downloadAndOptimizeImage = downloadAndOptimizeImage; +const fs = require("fs"); +const path = require("path"); +const sharp = require("sharp"); +const uuid_1 = require("uuid"); +const BLOCKED_HOSTNAMES = /^(localhost|127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|0\.0\.0\.0|::1|fc00:|fe80:)/i; +async function downloadAndOptimizeImage(sourceUrl, destDir) { + const raw = sourceUrl.trim(); + const protocolNormalized = raw.startsWith('//') ? `https:${raw}` : raw; + let parsedUrl; + try { + parsedUrl = new URL(protocolNormalized); + } + catch { + throw new Error('Ogiltig bild-URL'); + } + if (parsedUrl.protocol !== 'https:') { + throw new Error('Bild-URL måste använda https://'); + } + const hostname = parsedUrl.hostname; + if (BLOCKED_HOSTNAMES.test(hostname)) { + throw new Error('Bild-URL pekar på ett blockerat nätverk'); + } + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 10_000); + let response; + try { + response = await fetch(parsedUrl.toString(), { + signal: controller.signal, + headers: { 'User-Agent': 'Mozilla/5.0 (compatible; RecipeApp/1.0)' }, + }); + } + finally { + clearTimeout(timeout); + } + if (!response.ok) { + throw new Error(`HTTP ${response.status} vid nedladdning av bild`); + } + const contentType = response.headers.get('content-type') ?? ''; + if (!contentType.startsWith('image/')) { + throw new Error(`Ogiltig content-type: ${contentType}`); + } + const arrayBuffer = await response.arrayBuffer(); + if (arrayBuffer.byteLength > 5 * 1024 * 1024) { + throw new Error('Bilden är för stor (max 5 MB)'); + } + const imageBuffer = Buffer.from(arrayBuffer); + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + const filename = `${(0, uuid_1.v4)()}.jpg`; + const outputPath = path.join(destDir, filename); + await sharp(imageBuffer) + .resize(1200, 800, { + fit: 'inside', + withoutEnlargement: true, + }) + .jpeg({ quality: 80 }) + .toFile(outputPath); + return `/images/${filename}`; +} +//# sourceMappingURL=download-image.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/download-image.js.map b/backend/dist/common/utils/download-image.js.map new file mode 100644 index 00000000..a418a3a6 --- /dev/null +++ b/backend/dist/common/utils/download-image.js.map @@ -0,0 +1 @@ +{"version":3,"file":"download-image.js","sourceRoot":"","sources":["../../../src/common/utils/download-image.ts"],"names":[],"mappings":";;AAkBA,4DA2EC;AA7FD,yBAAyB;AACzB,6BAA6B;AAC7B,+BAA+B;AAC/B,+BAAoC;AAGpC,MAAM,iBAAiB,GAAG,0FAA0F,CAAC;AAY9G,KAAK,UAAU,wBAAwB,CAC5C,SAAiB,EACjB,OAAe;IAEf,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,kBAAkB,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAGvE,IAAI,SAAc,CAAC;IACnB,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAGD,IAAI,SAAS,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;IAEpC,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAGD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;IAC7D,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE;YAC3C,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE,EAAE,YAAY,EAAE,yCAAyC,EAAE;SACrE,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,0BAA0B,CAAC,CAAC;IACrE,CAAC;IAGD,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,yBAAyB,WAAW,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IACjD,IAAI,WAAW,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAG7C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,IAAA,SAAM,GAAE,MAAM,CAAC;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAGhD,MAAO,KAAmD,CAAC,WAAW,CAAC;SACpE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACjB,GAAG,EAAE,QAAQ;QACb,kBAAkB,EAAE,IAAI;KACzB,CAAC;SACD,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;SACrB,MAAM,CAAC,UAAU,CAAC,CAAC;IAGtB,OAAO,WAAW,QAAQ,EAAE,CAAC;AAC/B,CAAC"} \ No newline at end of file diff --git a/backend/dist/common/utils/normalize-name.d.ts b/backend/dist/common/utils/normalize-name.d.ts new file mode 100644 index 00000000..f36f389b --- /dev/null +++ b/backend/dist/common/utils/normalize-name.d.ts @@ -0,0 +1 @@ +export declare function normalizeName(value: string): string; diff --git a/backend/dist/common/utils/normalize-name.js b/backend/dist/common/utils/normalize-name.js new file mode 100644 index 00000000..97b904bc --- /dev/null +++ b/backend/dist/common/utils/normalize-name.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.normalizeName = normalizeName; +function normalizeName(value) { + return value + .trim() + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-z0-9\s]/g, '') + .replace(/\s+/g, ''); +} +//# sourceMappingURL=normalize-name.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/normalize-name.js.map b/backend/dist/common/utils/normalize-name.js.map new file mode 100644 index 00000000..de963365 --- /dev/null +++ b/backend/dist/common/utils/normalize-name.js.map @@ -0,0 +1 @@ +{"version":3,"file":"normalize-name.js","sourceRoot":"","sources":["../../../src/common/utils/normalize-name.ts"],"names":[],"mappings":";;AAAA,sCAQC;AARD,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC"} \ No newline at end of file diff --git a/backend/dist/common/utils/normalize-name.spec.d.ts b/backend/dist/common/utils/normalize-name.spec.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/backend/dist/common/utils/normalize-name.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/backend/dist/common/utils/normalize-name.spec.js b/backend/dist/common/utils/normalize-name.spec.js new file mode 100644 index 00000000..0c445faf --- /dev/null +++ b/backend/dist/common/utils/normalize-name.spec.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const normalize_name_1 = require("./normalize-name"); +describe('normalizeName', () => { + it('gör om till gemener', () => { + expect((0, normalize_name_1.normalizeName)('Kycklingfilé')).toBe('kycklingfile'); + }); + it('tar bort svenska diakritiska tecken', () => { + expect((0, normalize_name_1.normalizeName)('åäö')).toBe('aao'); + expect((0, normalize_name_1.normalizeName)('ÅÄÖ')).toBe('aao'); + }); + it('tar bort mellanslag', () => { + expect((0, normalize_name_1.normalizeName)('rökt paprikapulver')).toBe('roktpaprikapulver'); + }); + it('tar bort inledande och avslutande mellanslag', () => { + expect((0, normalize_name_1.normalizeName)(' lax ')).toBe('lax'); + }); + it('tar bort specialtecken', () => { + expect((0, normalize_name_1.normalizeName)('Curry (mild)')).toBe('currymild'); + }); + it('hanterar siffror', () => { + expect((0, normalize_name_1.normalizeName)('Omega-3')).toBe('omega3'); + }); + it('hanterar tom sträng', () => { + expect((0, normalize_name_1.normalizeName)('')).toBe(''); + }); + it('hanterar sträng med bara mellanslag', () => { + expect((0, normalize_name_1.normalizeName)(' ')).toBe(''); + }); + it('normaliserar accenter korrekt', () => { + expect((0, normalize_name_1.normalizeName)('Fläskfilé')).toBe('flaskfile'); + expect((0, normalize_name_1.normalizeName)('Gräddfil')).toBe('graddfil'); + expect((0, normalize_name_1.normalizeName)('Rödlök')).toBe('rodlok'); + }); +}); +//# sourceMappingURL=normalize-name.spec.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/normalize-name.spec.js.map b/backend/dist/common/utils/normalize-name.spec.js.map new file mode 100644 index 00000000..7792776f --- /dev/null +++ b/backend/dist/common/utils/normalize-name.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"normalize-name.spec.js","sourceRoot":"","sources":["../../../src/common/utils/normalize-name.spec.ts"],"names":[],"mappings":";;AAAA,qDAAiD;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,8BAAa,EAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,IAAA,8BAAa,EAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,IAAA,8BAAa,EAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,IAAA,8BAAa,EAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,IAAA,8BAAa,EAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,MAAM,CAAC,IAAA,8BAAa,EAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,MAAM,CAAC,IAAA,8BAAa,EAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/backend/dist/common/utils/recipe-parser.d.ts b/backend/dist/common/utils/recipe-parser.d.ts new file mode 100644 index 00000000..f741a2b5 --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.d.ts @@ -0,0 +1,14 @@ +export interface ParsedIngredient { + rawName: string; + quantity: number; + unit: string; + note: string | null; + alternatives: string[]; +} +export interface ParsedRecipe { + name: string; + description: string; + instructions: string; + ingredients: ParsedIngredient[]; +} +export declare function parseRecipeMarkdown(markdown: string): ParsedRecipe; diff --git a/backend/dist/common/utils/recipe-parser.js b/backend/dist/common/utils/recipe-parser.js new file mode 100644 index 00000000..935c2410 --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.js @@ -0,0 +1,124 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseRecipeMarkdown = parseRecipeMarkdown; +const KNOWN_UNITS = new Set([ + 'g', 'kg', 'hg', 'mg', 'ml', 'dl', 'l', 'tl', + 'st', 'tsk', 'msk', 'krm', 'matsled', 'tesled', + 'pris', 'portion', 'port', 'burk', 'förp', 'paket', 'efter smak', 'klyfta', +]); +const H1_RE = /^#\s+/; +const H2_RE = /^##\s+/; +const INGREDIENT_HEADING_RE = /ingrediens/; +const INSTRUCTION_HEADING_RE = /instruktion|tillagning|gör så här|steg|tillväg|metod/; +const BULLET_RE = /^[-*]\s+/; +const PAREN_NOTE_RE = /\(([^)]+)\)\s*$/; +const FRACTION_RE = /^(\d+)?\s*(\d+)\s*\/\s*([\d.]+)\s+(\S+)\s+(.+)$/; +const RANGE_RE = /^(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s*[-–]\s*(?:ca\.?\s+)?(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/i; +const QTY_UNIT_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(\S+)\s+(.+)$/; +const QTY_NAME_RE = /^(\d+(?:[.,]\d+)?)\s+(.+)$/; +const ALTERNATIVES_RE = /\s+eller\s+/i; +function parseRecipeMarkdown(markdown) { + const lines = markdown.split('\n'); + let name = ''; + let description = ''; + let instructions = ''; + const ingredients = []; + let currentSection = 'none'; + const descriptionLines = []; + const instructionLines = []; + for (const line of lines) { + const trimmed = line.trim(); + if (H1_RE.test(trimmed) && !trimmed.startsWith('##')) { + name = trimmed.replace(H1_RE, '').trim(); + currentSection = 'description'; + continue; + } + if (H2_RE.test(trimmed)) { + const heading = trimmed.replace(H2_RE, '').trim().toLowerCase(); + if (INGREDIENT_HEADING_RE.test(heading)) { + currentSection = 'ingredients'; + } + else if (INSTRUCTION_HEADING_RE.test(heading)) { + currentSection = 'instructions'; + } + else { + currentSection = 'none'; + } + continue; + } + switch (currentSection) { + case 'description': + if (trimmed.length > 0) { + descriptionLines.push(trimmed); + } + break; + case 'ingredients': + if (BULLET_RE.test(trimmed)) { + const ingredientText = trimmed.replace(BULLET_RE, ''); + ingredients.push(parseIngredientLine(ingredientText)); + } + break; + case 'instructions': + if (trimmed.length > 0) { + instructionLines.push(trimmed); + } + break; + } + } + description = descriptionLines.join('\n'); + instructions = instructionLines.join('\n'); + return { name, description, instructions, ingredients }; +} +function parseIngredientLine(text) { + const trimmed = text.trim(); + const toAlternatives = (rawName) => ALTERNATIVES_RE.test(rawName) + ? rawName.split(ALTERNATIVES_RE).map((s) => s.trim()).filter(Boolean) + : [rawName]; + let note = null; + let main = trimmed; + const parenMatch = trimmed.match(PAREN_NOTE_RE); + if (parenMatch) { + note = parenMatch[1].trim(); + main = trimmed.slice(0, parenMatch.index).trim(); + } + const fractionMatch = main.match(FRACTION_RE); + if (fractionMatch) { + const whole = fractionMatch[1] ? parseFloat(fractionMatch[1]) : 0; + const quantity = whole + parseFloat(fractionMatch[2]) / parseFloat(fractionMatch[3]); + const candidateUnit = fractionMatch[4].toLowerCase(); + if (KNOWN_UNITS.has(candidateUnit)) { + const rawName = fractionMatch[5].trim(); + return { quantity, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) }; + } + } + const rangeMatch = main.match(RANGE_RE); + if (rangeMatch) { + const candidateUnit = rangeMatch[3].toLowerCase(); + if (KNOWN_UNITS.has(candidateUnit)) { + const lo = parseNumber(rangeMatch[1]); + const hi = parseNumber(rangeMatch[2]); + const rawName = rangeMatch[4].trim(); + return { quantity: (lo + hi) / 2, unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) }; + } + } + const fullMatch = main.match(QTY_UNIT_NAME_RE); + if (fullMatch) { + const candidateUnit = fullMatch[2].toLowerCase(); + if (KNOWN_UNITS.has(candidateUnit)) { + const rawName = fullMatch[3].trim(); + return { quantity: parseNumber(fullMatch[1]), unit: candidateUnit, rawName, note, alternatives: toAlternatives(rawName) }; + } + const rawName = fullMatch[2] + ' ' + fullMatch[3]; + return { quantity: parseNumber(fullMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) }; + } + const noUnitMatch = main.match(QTY_NAME_RE); + if (noUnitMatch) { + const rawName = noUnitMatch[2].trim(); + return { quantity: parseNumber(noUnitMatch[1]), unit: 'st', rawName, note, alternatives: toAlternatives(rawName) }; + } + return { quantity: 0, unit: '', rawName: main, note, alternatives: toAlternatives(main) }; +} +function parseNumber(s) { + return parseFloat(s.replace(',', '.')); +} +//# sourceMappingURL=recipe-parser.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/recipe-parser.js.map b/backend/dist/common/utils/recipe-parser.js.map new file mode 100644 index 00000000..79a4a66d --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipe-parser.js","sourceRoot":"","sources":["../../../src/common/utils/recipe-parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/common/utils/recipe-parser.spec.d.ts b/backend/dist/common/utils/recipe-parser.spec.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/backend/dist/common/utils/recipe-parser.spec.js b/backend/dist/common/utils/recipe-parser.spec.js new file mode 100644 index 00000000..2339f22a --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.spec.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipe_parser_1 = require("./recipe-parser"); +describe('parseRecipeMarkdown — ingrediensformat', () => { + const parse = (line) => { + const md = `# Test\n## Ingredienser\n- ${line}\n## Instruktioner\n1. Gör det.`; + return (0, recipe_parser_1.parseRecipeMarkdown)(md).ingredients[0]; + }; + it('vanlig rad: kvantitet enhet namn', () => { + const ing = parse('400 g kycklingfilé'); + expect(ing).toMatchObject({ quantity: 400, unit: 'g', rawName: 'kycklingfilé' }); + }); + it('bråk: 1 1/2 dl grädde', () => { + const ing = parse('1 1/2 dl grädde'); + expect(ing.quantity).toBeCloseTo(1.5); + expect(ing).toMatchObject({ unit: 'dl', rawName: 'grädde' }); + }); + it('bråk utan heltal: 1/2 dl mjölk', () => { + const ing = parse('1/2 dl mjölk'); + expect(ing.quantity).toBeCloseTo(0.5); + expect(ing).toMatchObject({ unit: 'dl', rawName: 'mjölk' }); + }); + it('intervall: 600 - ca 700 g kycklingfilé', () => { + const ing = parse('600 - ca 700 g kycklingfilé'); + expect(ing.quantity).toBeCloseTo(650); + expect(ing).toMatchObject({ unit: 'g', rawName: 'kycklingfilé' }); + }); + it('intervall: ca 600-700 g kycklingfilé', () => { + const ing = parse('ca 600-700 g kycklingfilé'); + expect(ing.quantity).toBeCloseTo(650); + expect(ing).toMatchObject({ unit: 'g' }); + }); + it('intervall med alternativt namn: 600 - ca 700 g kycklingfilé eller kycklinginnerfilé', () => { + const ing = parse('600 - ca 700 g kycklingfilé eller kycklinginnerfilé'); + expect(ing.quantity).toBeCloseTo(650); + expect(ing).toMatchObject({ unit: 'g', rawName: 'kycklingfilé eller kycklinginnerfilé' }); + }); + it('parentes-not: 2 dl grädde (eller crème fraiche)', () => { + const ing = parse('2 dl grädde (eller crème fraiche)'); + expect(ing).toMatchObject({ quantity: 2, unit: 'dl', rawName: 'grädde', note: 'eller crème fraiche' }); + }); + it('utan enhet: 3 ägg', () => { + const ing = parse('3 ägg'); + expect(ing).toMatchObject({ quantity: 3, unit: 'st', rawName: 'ägg' }); + }); + it('bara namn: salt', () => { + const ing = parse('salt'); + expect(ing).toMatchObject({ quantity: 0, unit: '', rawName: 'salt' }); + }); + it('decimalkomma: 2,5 dl mjölk', () => { + const ing = parse('2,5 dl mjölk'); + expect(ing.quantity).toBeCloseTo(2.5); + expect(ing).toMatchObject({ unit: 'dl', rawName: 'mjölk' }); + }); +}); +//# sourceMappingURL=recipe-parser.spec.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/recipe-parser.spec.js.map b/backend/dist/common/utils/recipe-parser.spec.js.map new file mode 100644 index 00000000..e5991621 --- /dev/null +++ b/backend/dist/common/utils/recipe-parser.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipe-parser.spec.js","sourceRoot":"","sources":["../../../src/common/utils/recipe-parser.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/common/utils/units.d.ts b/backend/dist/common/utils/units.d.ts new file mode 100644 index 00000000..d493cfbf --- /dev/null +++ b/backend/dist/common/utils/units.d.ts @@ -0,0 +1,14 @@ +export type UnitType = 'weight' | 'volume' | 'cooking' | 'piece' | 'other'; +export interface UnitDefinition { + value: string; + labelSv: string; + type: UnitType; + toBaseFactor: number; + aliases: string[]; +} +export declare const UNIT_DEFINITIONS: UnitDefinition[]; +export declare function normalizeUnit(unit: string): string; +export declare function getUnitDefinition(unit: string): UnitDefinition | undefined; +export declare function getUnitType(unit: string): UnitType | null; +export declare function canConvert(fromUnit: string, toUnit: string): boolean; +export declare function convertUnit(quantity: number, fromUnit: string, toUnit: string): number; diff --git a/backend/dist/common/utils/units.js b/backend/dist/common/utils/units.js new file mode 100644 index 00000000..50e37b4b --- /dev/null +++ b/backend/dist/common/utils/units.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UNIT_DEFINITIONS = void 0; +exports.normalizeUnit = normalizeUnit; +exports.getUnitDefinition = getUnitDefinition; +exports.getUnitType = getUnitType; +exports.canConvert = canConvert; +exports.convertUnit = convertUnit; +exports.UNIT_DEFINITIONS = [ + { value: 'g', labelSv: 'g (gram)', type: 'weight', toBaseFactor: 1, aliases: ['gram'] }, + { value: 'hg', labelSv: 'hg (hektogram)', type: 'weight', toBaseFactor: 100, aliases: ['hektogram'] }, + { value: 'kg', labelSv: 'kg (kilogram)', type: 'weight', toBaseFactor: 1000, aliases: ['kilo', 'kilogram'] }, + { value: 'mg', labelSv: 'mg (milligram)', type: 'weight', toBaseFactor: 0.001, aliases: ['milligram'] }, + { value: 'ml', labelSv: 'ml (milliliter)', type: 'volume', toBaseFactor: 1, aliases: ['milliliter'] }, + { value: 'cl', labelSv: 'cl (centiliter)', type: 'volume', toBaseFactor: 10, aliases: ['centiliter'] }, + { value: 'dl', labelSv: 'dl (deciliter)', type: 'volume', toBaseFactor: 100, aliases: ['deciliter'] }, + { value: 'l', labelSv: 'l (liter)', type: 'volume', toBaseFactor: 1000, aliases: ['liter'] }, + { value: 'krm', labelSv: 'krm (kryddmått)', type: 'cooking', toBaseFactor: 1, aliases: ['kryddmatt', 'kryddmått'] }, + { value: 'tsk', labelSv: 'tsk (tesked)', type: 'cooking', toBaseFactor: 5, aliases: ['tesked', 'test'] }, + { value: 'msk', labelSv: 'msk (matsked)', type: 'cooking', toBaseFactor: 15, aliases: ['matsked', 'matsled'] }, + { value: 'st', labelSv: 'st (styck)', type: 'piece', toBaseFactor: 1, aliases: ['stycke', 'styck', 'stk'] }, + { value: 'port', labelSv: 'port (portioner)', type: 'piece', toBaseFactor: 1, aliases: ['portion', 'portioner'] }, + { value: 'förp', labelSv: 'förp (förpackning)', type: 'piece', toBaseFactor: 1, aliases: ['forp', 'förpackning', 'forpackning'] }, + { value: 'klyfta', labelSv: 'klyfta', type: 'piece', toBaseFactor: 1, aliases: [] }, + { value: 'efter smak', labelSv: 'efter smak', type: 'other', toBaseFactor: 1, aliases: ['eftr smak', 'efter smak'] }, +]; +const _unitLookup = new Map(); +for (const def of exports.UNIT_DEFINITIONS) { + _unitLookup.set(def.value.toLowerCase(), def); + for (const alias of def.aliases) { + _unitLookup.set(alias.toLowerCase(), def); + } +} +function normalizeUnit(unit) { + const key = unit.trim().toLowerCase(); + return _unitLookup.get(key)?.value ?? key; +} +function getUnitDefinition(unit) { + const key = unit.trim().toLowerCase(); + return _unitLookup.get(key); +} +function getUnitType(unit) { + return getUnitDefinition(unit)?.type ?? null; +} +function canConvert(fromUnit, toUnit) { + const from = getUnitDefinition(fromUnit); + const to = getUnitDefinition(toUnit); + if (!from || !to) + return false; + if (from.type !== to.type) + return false; + if (from.type === 'piece' || from.type === 'other') + return false; + return true; +} +function convertUnit(quantity, fromUnit, toUnit) { + if (quantity <= 0) { + throw new Error(`Invalid quantity: ${quantity}. Quantity must be positive.`); + } + const normalizedFrom = normalizeUnit(fromUnit); + const normalizedTo = normalizeUnit(toUnit); + if (normalizedFrom === normalizedTo) + return quantity; + const fromDef = getUnitDefinition(normalizedFrom); + const toDef = getUnitDefinition(normalizedTo); + if (!fromDef) + throw new Error(`Unknown unit: "${fromUnit}"`); + if (!toDef) + throw new Error(`Unknown unit: "${toUnit}"`); + if (fromDef.type !== toDef.type) { + throw new Error(`Cannot convert between incompatible unit types: "${fromUnit}" (${fromDef.type}) and "${toUnit}" (${toDef.type})`); + } + if (fromDef.type === 'piece' || fromDef.type === 'other') { + return quantity; + } + return (quantity * fromDef.toBaseFactor) / toDef.toBaseFactor; +} +//# sourceMappingURL=units.js.map \ No newline at end of file diff --git a/backend/dist/common/utils/units.js.map b/backend/dist/common/utils/units.js.map new file mode 100644 index 00000000..22fbacd6 --- /dev/null +++ b/backend/dist/common/utils/units.js.map @@ -0,0 +1 @@ +{"version":3,"file":"units.js","sourceRoot":"","sources":["../../../src/common/utils/units.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/health/health.controller.d.ts b/backend/dist/health/health.controller.d.ts new file mode 100644 index 00000000..5933709a --- /dev/null +++ b/backend/dist/health/health.controller.d.ts @@ -0,0 +1,8 @@ +import { Response } from 'express'; +import { HealthService } from './health.service'; +export declare class HealthController { + private readonly healthService; + constructor(healthService: HealthService); + getHealth(res: Response): Promise; + getDatabaseHealth(res: Response): Promise; +} diff --git a/backend/dist/health/health.controller.js b/backend/dist/health/health.controller.js new file mode 100644 index 00000000..e15d2ae6 --- /dev/null +++ b/backend/dist/health/health.controller.js @@ -0,0 +1,64 @@ +"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.HealthController = void 0; +const common_1 = require("@nestjs/common"); +const health_service_1 = require("./health.service"); +const public_decorator_1 = require("../auth/decorators/public.decorator"); +let HealthController = class HealthController { + constructor(healthService) { + this.healthService = healthService; + } + async getHealth(res) { + const health = await this.healthService.getOverallHealth(); + res.status(health.statusCode).json({ + status: health.status, + service: health.service, + timestamp: health.timestamp, + uptime: health.uptime, + checks: health.checks, + }); + } + async getDatabaseHealth(res) { + const dbHealth = await this.healthService.getDatabaseHealth(); + res.status(dbHealth.statusCode).json({ + status: dbHealth.status, + database: dbHealth.database, + responseTime: `${dbHealth.responseTime}ms`, + timestamp: dbHealth.timestamp, + details: dbHealth.details, + }); + } +}; +exports.HealthController = HealthController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], HealthController.prototype, "getHealth", null); +__decorate([ + (0, common_1.Get)('db'), + __param(0, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], HealthController.prototype, "getDatabaseHealth", null); +exports.HealthController = HealthController = __decorate([ + (0, public_decorator_1.Public)(), + (0, common_1.Controller)('health'), + __metadata("design:paramtypes", [health_service_1.HealthService]) +], HealthController); +//# sourceMappingURL=health.controller.js.map \ No newline at end of file diff --git a/backend/dist/health/health.controller.js.map b/backend/dist/health/health.controller.js.map new file mode 100644 index 00000000..ebbe5f28 --- /dev/null +++ b/backend/dist/health/health.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"health.controller.js","sourceRoot":"","sources":["../../src/health/health.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAgE;AAEhE,qDAAiD;AACjD,0EAA6D;AAItD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAOvD,AAAN,KAAK,CAAC,SAAS,CAAQ,GAAa;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC;QAC3D,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACjC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IAOK,AAAN,KAAK,CAAC,iBAAiB,CAAQ,GAAa;QAC1C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC;QAC9D,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACnC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,YAAY,EAAE,GAAG,QAAQ,CAAC,YAAY,IAAI;YAC1C,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;SAC1B,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAlCY,4CAAgB;AAQrB;IADL,IAAA,YAAG,GAAE;IACW,WAAA,IAAA,YAAG,GAAE,CAAA;;;;iDASrB;AAOK;IADL,IAAA,YAAG,EAAC,IAAI,CAAC;IACe,WAAA,IAAA,YAAG,GAAE,CAAA;;;;yDAS7B;2BAjCU,gBAAgB;IAF5B,IAAA,yBAAM,GAAE;IACR,IAAA,mBAAU,EAAC,QAAQ,CAAC;qCAEyB,8BAAa;GAD9C,gBAAgB,CAkC5B"} \ No newline at end of file diff --git a/backend/dist/health/health.module.d.ts b/backend/dist/health/health.module.d.ts new file mode 100644 index 00000000..1132c079 --- /dev/null +++ b/backend/dist/health/health.module.d.ts @@ -0,0 +1,2 @@ +export declare class HealthModule { +} diff --git a/backend/dist/health/health.module.js b/backend/dist/health/health.module.js new file mode 100644 index 00000000..9fd589a6 --- /dev/null +++ b/backend/dist/health/health.module.js @@ -0,0 +1,24 @@ +"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.HealthModule = void 0; +const common_1 = require("@nestjs/common"); +const health_controller_1 = require("./health.controller"); +const health_service_1 = require("./health.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let HealthModule = class HealthModule { +}; +exports.HealthModule = HealthModule; +exports.HealthModule = HealthModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [health_controller_1.HealthController], + providers: [health_service_1.HealthService], + }) +], HealthModule); +//# sourceMappingURL=health.module.js.map \ No newline at end of file diff --git a/backend/dist/health/health.module.js.map b/backend/dist/health/health.module.js.map new file mode 100644 index 00000000..a1fad9e5 --- /dev/null +++ b/backend/dist/health/health.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"health.module.js","sourceRoot":"","sources":["../../src/health/health.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AACjD,2DAAuD;AAOhD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;KAC3B,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/backend/dist/health/health.service.d.ts b/backend/dist/health/health.service.d.ts new file mode 100644 index 00000000..4d6285d0 --- /dev/null +++ b/backend/dist/health/health.service.d.ts @@ -0,0 +1,41 @@ +import { PrismaService } from '../prisma/prisma.service'; +export interface HealthStatus { + status: 'healthy' | 'degraded' | 'unhealthy'; + service: string; + timestamp: string; + uptime: number; + checks: { + database: { + status: 'ok' | 'error'; + responseTime: number; + details?: string; + }; + }; +} +export declare class HealthService { + private readonly prisma; + private readonly startTime; + constructor(prisma: PrismaService); + getOverallHealth(): Promise<{ + status: 'healthy' | 'degraded' | 'unhealthy'; + statusCode: number; + service: string; + timestamp: string; + uptime: number; + checks: { + database: { + status: 'ok' | 'error'; + responseTime: number; + details?: string; + }; + }; + }>; + getDatabaseHealth(): Promise<{ + status: 'ok' | 'error'; + database: string; + responseTime: number; + timestamp: string; + details?: string; + statusCode: number; + }>; +} diff --git a/backend/dist/health/health.service.js b/backend/dist/health/health.service.js new file mode 100644 index 00000000..c0dfd190 --- /dev/null +++ b/backend/dist/health/health.service.js @@ -0,0 +1,89 @@ +"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.HealthService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +let HealthService = class HealthService { + constructor(prisma) { + this.prisma = prisma; + this.startTime = Date.now(); + } + async getOverallHealth() { + const timestamp = new Date().toISOString(); + const uptime = Date.now() - this.startTime; + const dbStart = Date.now(); + let dbStatus = 'ok'; + let dbResponseTime = 0; + let dbDetails; + try { + await this.prisma.$queryRaw `SELECT 1`; + dbResponseTime = Date.now() - dbStart; + } + catch (error) { + dbStatus = 'error'; + dbResponseTime = Date.now() - dbStart; + dbDetails = error instanceof Error ? error.message : 'Unknown database error'; + } + let overallStatus = 'healthy'; + if (dbStatus === 'error') { + overallStatus = 'unhealthy'; + } + const statusCode = overallStatus === 'unhealthy' ? 503 : 200; + return { + status: overallStatus, + statusCode, + service: 'recipe-api', + timestamp, + uptime, + checks: { + database: { + status: dbStatus, + responseTime: dbResponseTime, + details: dbDetails, + }, + }, + }; + } + async getDatabaseHealth() { + const timestamp = new Date().toISOString(); + const startTime = Date.now(); + try { + await this.prisma.$queryRaw `SELECT 1`; + const responseTime = Date.now() - startTime; + return { + status: 'ok', + database: 'connected', + responseTime, + timestamp, + statusCode: 200, + }; + } + catch (error) { + const responseTime = Date.now() - startTime; + const details = error instanceof Error ? error.message : 'Unknown database error'; + return { + status: 'error', + database: 'not reachable', + responseTime, + timestamp, + details, + statusCode: 503, + }; + } + } +}; +exports.HealthService = HealthService; +exports.HealthService = HealthService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], HealthService); +//# sourceMappingURL=health.service.js.map \ No newline at end of file diff --git a/backend/dist/health/health.service.js.map b/backend/dist/health/health.service.js.map new file mode 100644 index 00000000..630b4202 --- /dev/null +++ b/backend/dist/health/health.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"health.service.js","sourceRoot":"","sources":["../../src/health/health.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAiBlD,IAAM,aAAa,GAAnB,MAAM,aAAa;IAGxB,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QAFjC,cAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEa,CAAC;IAEtD,KAAK,CAAC,gBAAgB;QAcpB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QAG3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,QAAQ,GAAmB,IAAI,CAAC;QACpC,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,SAA6B,CAAC;QAElC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,UAAU,CAAC;YACtC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,GAAG,OAAO,CAAC;YACnB,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YACtC,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;QAChF,CAAC;QAGD,IAAI,aAAa,GAAyC,SAAS,CAAC;QACpE,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACzB,aAAa,GAAG,WAAW,CAAC;QAC9B,CAAC;QAED,MAAM,UAAU,GAAG,aAAa,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAE7D,OAAO;YACL,MAAM,EAAE,aAAa;YACrB,UAAU;YACV,OAAO,EAAE,YAAY;YACrB,SAAS;YACT,MAAM;YACN,MAAM,EAAE;gBACN,QAAQ,EAAE;oBACR,MAAM,EAAE,QAAQ;oBAChB,YAAY,EAAE,cAAc;oBAC5B,OAAO,EAAE,SAAS;iBACnB;aACF;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB;QAQrB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,UAAU,CAAC;YACtC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE5C,OAAO;gBACL,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,WAAW;gBACrB,YAAY;gBACZ,SAAS;gBACT,UAAU,EAAE,GAAG;aAChB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC5C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC;YAElF,OAAO;gBACL,MAAM,EAAE,OAAO;gBACf,QAAQ,EAAE,eAAe;gBACzB,YAAY;gBACZ,SAAS;gBACT,OAAO;gBACP,UAAU,EAAE,GAAG;aAChB,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAA;AAjGY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAI0B,8BAAa;GAHvC,aAAa,CAiGzB"} \ No newline at end of file diff --git a/backend/dist/inventory/dto/consume-inventory.dto.d.ts b/backend/dist/inventory/dto/consume-inventory.dto.d.ts new file mode 100644 index 00000000..bcd83ce0 --- /dev/null +++ b/backend/dist/inventory/dto/consume-inventory.dto.d.ts @@ -0,0 +1,4 @@ +export declare class ConsumeInventoryDto { + amountUsed: number; + comment?: string; +} diff --git a/backend/dist/inventory/dto/consume-inventory.dto.js b/backend/dist/inventory/dto/consume-inventory.dto.js new file mode 100644 index 00000000..7610d3ec --- /dev/null +++ b/backend/dist/inventory/dto/consume-inventory.dto.js @@ -0,0 +1,27 @@ +"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.ConsumeInventoryDto = void 0; +const class_validator_1 = require("class-validator"); +class ConsumeInventoryDto { +} +exports.ConsumeInventoryDto = ConsumeInventoryDto; +__decorate([ + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0.01), + __metadata("design:type", Number) +], ConsumeInventoryDto.prototype, "amountUsed", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], ConsumeInventoryDto.prototype, "comment", void 0); +//# sourceMappingURL=consume-inventory.dto.js.map \ No newline at end of file diff --git a/backend/dist/inventory/dto/consume-inventory.dto.js.map b/backend/dist/inventory/dto/consume-inventory.dto.js.map new file mode 100644 index 00000000..cfe5d5ad --- /dev/null +++ b/backend/dist/inventory/dto/consume-inventory.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"consume-inventory.dto.js","sourceRoot":"","sources":["../../../src/inventory/dto/consume-inventory.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAsE;AAEtE,MAAa,mBAAmB;CAQ/B;AARD,kDAQC;AALC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,IAAI,CAAC;;uDACU;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACM"} \ No newline at end of file diff --git a/backend/dist/inventory/dto/create-inventory.dto.d.ts b/backend/dist/inventory/dto/create-inventory.dto.d.ts new file mode 100644 index 00000000..196b4e8f --- /dev/null +++ b/backend/dist/inventory/dto/create-inventory.dto.d.ts @@ -0,0 +1,14 @@ +export declare class CreateInventoryDto { + productId: number; + quantity: number; + unit: string; + location?: string; + purchaseDate?: string; + bestBeforeDate?: string; + brand?: string; + origin?: string; + receiptName?: string; + opened?: boolean; + suitableFor?: string; + comment?: string; +} diff --git a/backend/dist/inventory/dto/create-inventory.dto.js b/backend/dist/inventory/dto/create-inventory.dto.js new file mode 100644 index 00000000..75a763f8 --- /dev/null +++ b/backend/dist/inventory/dto/create-inventory.dto.js @@ -0,0 +1,73 @@ +"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.CreateInventoryDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateInventoryDto { +} +exports.CreateInventoryDto = CreateInventoryDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateInventoryDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], CreateInventoryDto.prototype, "quantity", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "unit", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "location", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "purchaseDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "bestBeforeDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "brand", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "origin", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "receiptName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], CreateInventoryDto.prototype, "opened", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "suitableFor", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateInventoryDto.prototype, "comment", void 0); +//# sourceMappingURL=create-inventory.dto.js.map \ No newline at end of file diff --git a/backend/dist/inventory/dto/create-inventory.dto.js.map b/backend/dist/inventory/dto/create-inventory.dto.js.map new file mode 100644 index 00000000..04ad5bf6 --- /dev/null +++ b/backend/dist/inventory/dto/create-inventory.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-inventory.dto.js","sourceRoot":"","sources":["../../../src/inventory/dto/create-inventory.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAOyB;AAEzB,MAAa,kBAAkB;CA4C9B;AA5CD,gDA4CC;AA1CC;IADC,IAAA,uBAAK,GAAE;;qDACW;AAInB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;oDACW;AAGlB;IADC,IAAA,0BAAQ,GAAE;;gDACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACO;AAGlB;IADC,IAAA,4BAAU,GAAE;;wDACS;AAGtB;IADC,IAAA,4BAAU,GAAE;;0DACW;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;iDACI;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;kDACK;AAIhB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;kDACK;AAIjB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACM"} \ No newline at end of file diff --git a/backend/dist/inventory/dto/update-inventory.dto.d.ts b/backend/dist/inventory/dto/update-inventory.dto.d.ts new file mode 100644 index 00000000..38fb2069 --- /dev/null +++ b/backend/dist/inventory/dto/update-inventory.dto.d.ts @@ -0,0 +1,13 @@ +export declare class UpdateInventoryDto { + productId?: number; + quantity?: number; + unit?: string; + location?: string; + purchaseDate?: string; + bestBeforeDate?: string; + brand?: string; + receiptName?: string; + opened?: boolean; + suitableFor?: string; + comment?: string; +} diff --git a/backend/dist/inventory/dto/update-inventory.dto.js b/backend/dist/inventory/dto/update-inventory.dto.js new file mode 100644 index 00000000..9782c11b --- /dev/null +++ b/backend/dist/inventory/dto/update-inventory.dto.js @@ -0,0 +1,71 @@ +"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.UpdateInventoryDto = void 0; +const class_validator_1 = require("class-validator"); +class UpdateInventoryDto { +} +exports.UpdateInventoryDto = UpdateInventoryDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpdateInventoryDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], UpdateInventoryDto.prototype, "quantity", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "unit", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "location", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "purchaseDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "bestBeforeDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "brand", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "receiptName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], UpdateInventoryDto.prototype, "opened", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "suitableFor", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateInventoryDto.prototype, "comment", void 0); +//# sourceMappingURL=update-inventory.dto.js.map \ No newline at end of file diff --git a/backend/dist/inventory/dto/update-inventory.dto.js.map b/backend/dist/inventory/dto/update-inventory.dto.js.map new file mode 100644 index 00000000..e91df640 --- /dev/null +++ b/backend/dist/inventory/dto/update-inventory.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"update-inventory.dto.js","sourceRoot":"","sources":["../../../src/inventory/dto/update-inventory.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAOyB;AAEzB,MAAa,kBAAkB;CA2C9B;AA3CD,gDA2CC;AAxCC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;qDACW;AAKnB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;oDACW;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;gDACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACO;AAGlB;IADC,IAAA,4BAAU,GAAE;;wDACS;AAGtB;IADC,IAAA,4BAAU,GAAE;;0DACW;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;iDACI;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;kDACK;AAIjB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACM"} \ No newline at end of file diff --git a/backend/dist/inventory/inventory.controller.d.ts b/backend/dist/inventory/inventory.controller.d.ts new file mode 100644 index 00000000..a8f86a5e --- /dev/null +++ b/backend/dist/inventory/inventory.controller.d.ts @@ -0,0 +1,200 @@ +import { CreateInventoryDto } from './dto/create-inventory.dto'; +import { UpdateInventoryDto } from './dto/update-inventory.dto'; +import { InventoryService } from './inventory.service'; +import { ConsumeInventoryDto } from './dto/consume-inventory.dto'; +export declare class InventoryController { + private readonly inventoryService; + constructor(inventoryService: InventoryService); + consume(id: number, body: ConsumeInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + findConsumptionHistory(id: number): Promise<{ + inventoryItem: { + unit: string; + }; + id: number; + createdAt: Date; + comment: string | null; + amountUsed: import("@prisma/client/runtime/library").Decimal; + inventoryItemId: number; + }[]>; + findAll(location?: string, sort?: string): Promise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + })[]>; + findExpiring(): Promise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + })[]>; + create(body: CreateInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + update(id: number, body: UpdateInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + remove(id: number): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: import("@prisma/client/runtime/library").Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; +} diff --git a/backend/dist/inventory/inventory.controller.js b/backend/dist/inventory/inventory.controller.js new file mode 100644 index 00000000..526a991c --- /dev/null +++ b/backend/dist/inventory/inventory.controller.js @@ -0,0 +1,103 @@ +"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.InventoryController = void 0; +const common_1 = require("@nestjs/common"); +const create_inventory_dto_1 = require("./dto/create-inventory.dto"); +const update_inventory_dto_1 = require("./dto/update-inventory.dto"); +const inventory_service_1 = require("./inventory.service"); +const consume_inventory_dto_1 = require("./dto/consume-inventory.dto"); +let InventoryController = class InventoryController { + constructor(inventoryService) { + this.inventoryService = inventoryService; + } + consume(id, body) { + return this.inventoryService.consume(id, body); + } + findConsumptionHistory(id) { + return this.inventoryService.findConsumptionHistory(id); + } + findAll(location, sort) { + return this.inventoryService.findAll({ location, sort }); + } + findExpiring() { + return this.inventoryService.findExpiring(); + } + create(body) { + return this.inventoryService.create(body); + } + update(id, body) { + return this.inventoryService.update(id, body); + } + remove(id) { + return this.inventoryService.remove(id); + } +}; +exports.InventoryController = InventoryController; +__decorate([ + (0, common_1.Post)(':id/consume'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, consume_inventory_dto_1.ConsumeInventoryDto]), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "consume", null); +__decorate([ + (0, common_1.Get)(':id/consumption-history'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number]), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "findConsumptionHistory", null); +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Query)('location')), + __param(1, (0, common_1.Query)('sort')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, String]), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('expiring'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "findExpiring", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [create_inventory_dto_1.CreateInventoryDto]), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "create", null); +__decorate([ + (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_inventory_dto_1.UpdateInventoryDto]), + __metadata("design:returntype", void 0) +], InventoryController.prototype, "update", null); +__decorate([ + (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) +], InventoryController.prototype, "remove", null); +exports.InventoryController = InventoryController = __decorate([ + (0, common_1.Controller)('inventory'), + __metadata("design:paramtypes", [inventory_service_1.InventoryService]) +], InventoryController); +//# sourceMappingURL=inventory.controller.js.map \ No newline at end of file diff --git a/backend/dist/inventory/inventory.controller.js.map b/backend/dist/inventory/inventory.controller.js.map new file mode 100644 index 00000000..3216eef3 --- /dev/null +++ b/backend/dist/inventory/inventory.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"inventory.controller.js","sourceRoot":"","sources":["../../src/inventory/inventory.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,qEAAgE;AAChE,qEAAgE;AAChE,2DAAuD;AACvD,uEAAkE;AAG3D,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,YAA6B,gBAAkC;QAAlC,qBAAgB,GAAhB,gBAAgB,CAAkB;IAAG,CAAC;IAGpE,OAAO,CACsB,EAAU,EAC7B,IAAyB;QAElC,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAGH,sBAAsB,CAA4B,EAAU;QAC1D,OAAO,IAAI,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IAGD,OAAO,CACc,QAAiB,EACrB,IAAa;QAE5B,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAGD,YAAY;QACV,OAAO,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC;IAC9C,CAAC;IAGD,MAAM,CAAS,IAAwB;QACrC,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAGD,MAAM,CACuB,EAAU,EAC7B,IAAwB;QAEhC,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAGD,MAAM,CAA4B,EAAU;QAC1C,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;CACF,CAAA;AA9CY,kDAAmB;AAI/B;IADC,IAAA,aAAI,EAAC,aAAa,CAAC;IAEjB,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;IACzB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAO,2CAAmB;;kDAGjC;AAGH;IADG,IAAA,YAAG,EAAC,yBAAyB,CAAC;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;;;;iEAE9C;AAGD;IADC,IAAA,YAAG,GAAE;IAEH,WAAA,IAAA,cAAK,EAAC,UAAU,CAAC,CAAA;IACjB,WAAA,IAAA,cAAK,EAAC,MAAM,CAAC,CAAA;;;;kDAGf;AAGD;IADC,IAAA,YAAG,EAAC,UAAU,CAAC;;;;uDAGf;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAO,yCAAkB;;iDAEtC;AAGD;IADC,IAAA,cAAK,EAAC,KAAK,CAAC;IAEV,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;IACzB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAO,yCAAkB;;iDAGjC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;;;;iDAEhC;8BA7CU,mBAAmB;IAD/B,IAAA,mBAAU,EAAC,WAAW,CAAC;qCAEyB,oCAAgB;GADpD,mBAAmB,CA8C/B"} \ No newline at end of file diff --git a/backend/dist/inventory/inventory.module.d.ts b/backend/dist/inventory/inventory.module.d.ts new file mode 100644 index 00000000..6b3b1563 --- /dev/null +++ b/backend/dist/inventory/inventory.module.d.ts @@ -0,0 +1,2 @@ +export declare class InventoryModule { +} diff --git a/backend/dist/inventory/inventory.module.js b/backend/dist/inventory/inventory.module.js new file mode 100644 index 00000000..b04453bb --- /dev/null +++ b/backend/dist/inventory/inventory.module.js @@ -0,0 +1,24 @@ +"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.InventoryModule = void 0; +const common_1 = require("@nestjs/common"); +const inventory_controller_1 = require("./inventory.controller"); +const inventory_service_1 = require("./inventory.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let InventoryModule = class InventoryModule { +}; +exports.InventoryModule = InventoryModule; +exports.InventoryModule = InventoryModule = __decorate([ + (0, common_1.Module)({ + controllers: [inventory_controller_1.InventoryController], + providers: [inventory_service_1.InventoryService], + imports: [prisma_module_1.PrismaModule], + }) +], InventoryModule); +//# sourceMappingURL=inventory.module.js.map \ No newline at end of file diff --git a/backend/dist/inventory/inventory.module.js.map b/backend/dist/inventory/inventory.module.js.map new file mode 100644 index 00000000..ed93d98c --- /dev/null +++ b/backend/dist/inventory/inventory.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"inventory.module.js","sourceRoot":"","sources":["../../src/inventory/inventory.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA6D;AAC7D,2DAAuD;AACvD,2DAAuD;AAOhD,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAL3B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE,CAAC,oCAAgB,CAAC;QAC7B,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,eAAe,CAAG"} \ No newline at end of file diff --git a/backend/dist/inventory/inventory.service.d.ts b/backend/dist/inventory/inventory.service.d.ts new file mode 100644 index 00000000..8e012d9e --- /dev/null +++ b/backend/dist/inventory/inventory.service.d.ts @@ -0,0 +1,209 @@ +import { ConsumeInventoryDto } from './dto/consume-inventory.dto'; +import { Prisma } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateInventoryDto } from './dto/create-inventory.dto'; +import { UpdateInventoryDto } from './dto/update-inventory.dto'; +type InventoryQuery = { + location?: string; + sort?: string; +}; +export declare class InventoryService { + private prisma; + constructor(prisma: PrismaService); + private throwInventoryItemNotFound; + private findInventoryItemByIdOrThrow; + private ensureProductExists; + findAll(query?: InventoryQuery): Promise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + })[]>; + consume(id: number, data: ConsumeInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + findConsumptionHistory(id: number): Promise<{ + inventoryItem: { + unit: string; + }; + id: number; + createdAt: Date; + comment: string | null; + amountUsed: Prisma.Decimal; + inventoryItemId: number; + }[]>; + findExpiring(): Promise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + })[]>; + create(data: CreateInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + update(id: number, data: UpdateInventoryDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; + remove(id: number): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + quantity: Prisma.Decimal; + unit: string; + brand: string | null; + origin: string | null; + receiptName: string | null; + location: string | null; + purchaseDate: Date | null; + opened: boolean | null; + suitableFor: string | null; + bestBeforeDate: Date | null; + comment: string | null; + }>; +} +export {}; diff --git a/backend/dist/inventory/inventory.service.js b/backend/dist/inventory/inventory.service.js new file mode 100644 index 00000000..292e8909 --- /dev/null +++ b/backend/dist/inventory/inventory.service.js @@ -0,0 +1,216 @@ +"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.InventoryService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +const prisma_service_1 = require("../prisma/prisma.service"); +let InventoryService = class InventoryService { + constructor(prisma) { + this.prisma = prisma; + } + throwInventoryItemNotFound(id) { + throw new common_1.NotFoundException(`Inventory item with id ${id} not found`); + } + async findInventoryItemByIdOrThrow(id) { + const existing = await this.prisma.inventoryItem.findUnique({ where: { id } }); + if (!existing) { + this.throwInventoryItemNotFound(id); + } + return existing; + } + async ensureProductExists(productId) { + const product = await this.prisma.product.findUnique({ where: { id: productId } }); + if (!product) { + throw new common_1.NotFoundException('Product not found'); + } + return product; + } + async findAll(query) { + const where = {}; + const orderBy = []; + if (query?.location) { + where.location = query.location; + } + if (query?.sort === 'bestBeforeAsc') { + orderBy.push({ bestBeforeDate: 'asc' }); + } + else if (query?.sort === 'bestBeforeDesc') { + orderBy.push({ bestBeforeDate: 'desc' }); + } + else if (query?.sort === 'nameAsc') { + orderBy.push({ product: { name: 'asc' } }); + } + else if (query?.sort === 'purchaseDateAsc') { + orderBy.push({ purchaseDate: 'asc' }); + } + else if (query?.sort === 'purchaseDateDesc') { + orderBy.push({ purchaseDate: 'desc' }); + } + else { + orderBy.push({ createdAt: 'desc' }); + } + return this.prisma.inventoryItem.findMany({ + where, + include: { + product: true, + }, + orderBy, + }); + } + async consume(id, data) { + const existing = await this.findInventoryItemByIdOrThrow(id); + const currentQuantity = Number(existing.quantity); + const newQuantity = Math.max(0, currentQuantity - data.amountUsed); + return this.prisma.$transaction(async (tx) => { + const updatedItem = await tx.inventoryItem.update({ + where: { id }, + data: { + quantity: new client_1.Prisma.Decimal(newQuantity), + }, + include: { + product: true, + }, + }); + await tx.inventoryConsumption.create({ + data: { + inventoryItemId: id, + amountUsed: new client_1.Prisma.Decimal(data.amountUsed), + comment: data.comment?.trim() || null, + }, + }); + return updatedItem; + }); + } + async findConsumptionHistory(id) { + await this.findInventoryItemByIdOrThrow(id); + return this.prisma.inventoryConsumption.findMany({ + where: { + inventoryItemId: id, + }, + select: { + id: true, + inventoryItemId: true, + amountUsed: true, + comment: true, + createdAt: true, + inventoryItem: { + select: { unit: true }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }); + } + async findExpiring() { + const now = new Date(); + return this.prisma.inventoryItem.findMany({ + where: { + bestBeforeDate: { + not: null, + gte: now, + }, + }, + include: { + product: true, + }, + orderBy: [{ bestBeforeDate: 'asc' }, { createdAt: 'desc' }], + }); + } + async create(data) { + await this.ensureProductExists(data.productId); + return this.prisma.inventoryItem.create({ + data: { + ...data, + quantity: new client_1.Prisma.Decimal(data.quantity), + location: data.location?.trim() || undefined, + brand: data.brand?.trim() || undefined, + origin: data.origin?.trim() || undefined, + receiptName: data.receiptName?.trim() || undefined, + suitableFor: data.suitableFor?.trim() || undefined, + comment: data.comment?.trim() || undefined, + purchaseDate: data.purchaseDate + ? new Date(data.purchaseDate) + : undefined, + bestBeforeDate: data.bestBeforeDate + ? new Date(data.bestBeforeDate) + : undefined, + }, + include: { + product: true, + }, + }); + } + async update(id, data) { + await this.findInventoryItemByIdOrThrow(id); + if (typeof data.productId === 'number') { + await this.ensureProductExists(data.productId); + } + const updateData = {}; + if (typeof data.productId === 'number') { + updateData.product = { + connect: { id: data.productId }, + }; + } + if (typeof data.quantity === 'number') { + updateData.quantity = new client_1.Prisma.Decimal(data.quantity); + } + if (typeof data.unit === 'string') { + updateData.unit = data.unit.trim(); + } + if (typeof data.location === 'string') { + updateData.location = data.location.trim(); + } + if (typeof data.brand === 'string') { + updateData.brand = data.brand.trim(); + } + if (typeof data.receiptName === 'string') { + updateData.receiptName = data.receiptName.trim(); + } + if (typeof data.purchaseDate === 'string') { + updateData.purchaseDate = data.purchaseDate + ? new Date(data.purchaseDate) + : null; + } + if (typeof data.bestBeforeDate === 'string') { + updateData.bestBeforeDate = data.bestBeforeDate + ? new Date(data.bestBeforeDate) + : null; + } + if (typeof data.opened === 'boolean') { + updateData.opened = data.opened; + } + if (typeof data.suitableFor === 'string') { + updateData.suitableFor = data.suitableFor.trim(); + } + if (typeof data.comment === 'string') { + updateData.comment = data.comment.trim(); + } + return this.prisma.inventoryItem.update({ + where: { id }, + data: updateData, + include: { + product: true, + }, + }); + } + async remove(id) { + await this.findInventoryItemByIdOrThrow(id); + return this.prisma.inventoryItem.delete({ where: { id } }); + } +}; +exports.InventoryService = InventoryService; +exports.InventoryService = InventoryService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], InventoryService); +//# sourceMappingURL=inventory.service.js.map \ No newline at end of file diff --git a/backend/dist/inventory/inventory.service.js.map b/backend/dist/inventory/inventory.service.js.map new file mode 100644 index 00000000..e8256f1e --- /dev/null +++ b/backend/dist/inventory/inventory.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"inventory.service.js","sourceRoot":"","sources":["../../src/inventory/inventory.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/main.d.ts b/backend/dist/main.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/backend/dist/main.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/backend/dist/main.js b/backend/dist/main.js new file mode 100644 index 00000000..733537af --- /dev/null +++ b/backend/dist/main.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const app_module_1 = require("./app.module"); +const global_exception_filter_1 = require("./common/filters/global-exception.filter"); +const helmet_1 = require("helmet"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule); + const allowedOrigin = process.env.ALLOWED_ORIGIN || 'https://recept.gynther.se'; + app.enableCors({ + origin: allowedOrigin, + methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + }); + app.use((0, helmet_1.default)({ + contentSecurityPolicy: false, + crossOriginEmbedderPolicy: true, + crossOriginOpenerPolicy: { policy: 'same-origin' }, + crossOriginResourcePolicy: { policy: 'same-origin' }, + originAgentCluster: true, + referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, + strictTransportSecurity: { + maxAge: 31536000, + includeSubDomains: true, + preload: true, + }, + xContentTypeOptions: true, + xFrameOptions: { action: 'deny' }, + xXssProtection: false, + })); + app.setGlobalPrefix('api'); + app.useGlobalFilters(new global_exception_filter_1.GlobalExceptionFilter()); + app.useGlobalPipes(new common_1.ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + })); + await app.listen(8080, '0.0.0.0'); +} +bootstrap(); +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/backend/dist/main.js.map b/backend/dist/main.js.map new file mode 100644 index 00000000..ae91ac09 --- /dev/null +++ b/backend/dist/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,2CAAgD;AAChD,uCAA2C;AAC3C,6CAAyC;AACzC,sFAAiF;AACjF,mCAA4B;AAE5B,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAGhD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,2BAA2B,CAAC;IAChF,GAAG,CAAC,UAAU,CAAC;QACb,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;QAC7D,cAAc,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;QACjD,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAGH,GAAG,CAAC,GAAG,CACL,IAAA,gBAAM,EAAC;QACL,qBAAqB,EAAE,KAAK;QAC5B,yBAAyB,EAAE,IAAI;QAC/B,uBAAuB,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE;QAClD,yBAAyB,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE;QACpD,kBAAkB,EAAE,IAAI;QACxB,cAAc,EAAE,EAAE,MAAM,EAAE,iCAAiC,EAAE;QAC7D,uBAAuB,EAAE;YACvB,MAAM,EAAE,QAAQ;YAChB,iBAAiB,EAAE,IAAI;YACvB,OAAO,EAAE,IAAI;SACd;QACD,mBAAmB,EAAE,IAAI;QACzB,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;QACjC,cAAc,EAAE,KAAK;KACtB,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAG3B,GAAG,CAAC,gBAAgB,CAAC,IAAI,+CAAqB,EAAE,CAAC,CAAC;IAElD,GAAG,CAAC,cAAc,CAChB,IAAI,uBAAc,CAAC;QACjB,SAAS,EAAE,IAAI;QACf,oBAAoB,EAAE,IAAI;QAC1B,SAAS,EAAE,IAAI;KAChB,CAAC,CACH,CAAC;IAEF,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACpC,CAAC;AACD,SAAS,EAAE,CAAC"} \ No newline at end of file diff --git a/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.d.ts b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.d.ts new file mode 100644 index 00000000..9e1608f3 --- /dev/null +++ b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.d.ts @@ -0,0 +1,5 @@ +export declare class CreateMealPlanEntryDto { + date: string; + recipeId: number; + servings?: number; +} diff --git a/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js new file mode 100644 index 00000000..0a764d22 --- /dev/null +++ b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js @@ -0,0 +1,32 @@ +"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.CreateMealPlanEntryDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateMealPlanEntryDto { +} +exports.CreateMealPlanEntryDto = CreateMealPlanEntryDto; +__decorate([ + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateMealPlanEntryDto.prototype, "date", void 0); +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsPositive)(), + __metadata("design:type", Number) +], CreateMealPlanEntryDto.prototype, "recipeId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], CreateMealPlanEntryDto.prototype, "servings", void 0); +//# sourceMappingURL=create-meal-plan-entry.dto.js.map \ No newline at end of file diff --git a/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js.map b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js.map new file mode 100644 index 00000000..f31e355f --- /dev/null +++ b/backend/dist/meal-plan/dto/create-meal-plan-entry.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-meal-plan-entry.dto.js","sourceRoot":"","sources":["../../../src/meal-plan/dto/create-meal-plan-entry.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAmF;AAEnF,MAAa,sBAAsB;CAYlC;AAZD,wDAYC;AAVC;IADC,IAAA,8BAAY,GAAE;;oDACF;AAIb;IAFC,IAAA,uBAAK,GAAE;IACP,IAAA,4BAAU,GAAE;;wDACI;AAKjB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;wDACW"} \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.controller.d.ts b/backend/dist/meal-plan/meal-plan.controller.d.ts new file mode 100644 index 00000000..ef180f52 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.controller.d.ts @@ -0,0 +1,92 @@ +import { MealPlanService } from './meal-plan.service'; +import { CreateMealPlanEntryDto } from './dto/create-meal-plan-entry.dto'; +export declare class MealPlanController { + private readonly mealPlanService; + constructor(mealPlanService: MealPlanService); + findByRange(user: { + userId: number; + }, from: string, to: string): Promise<({ + recipe: { + name: string; + id: number; + imageUrl: string | null; + servings: number | null; + ingredients: { + product: { + name: string; + canonicalName: string | null; + id: number; + } | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + note: string | null; + }[]; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + })[]>; + shoppingList(user: { + userId: number; + }, from: string, to: string): Promise<{ + productId: number; + name: string; + quantity: number; + unit: string; + }[]>; + inventoryCompare(user: { + userId: number; + }, from: string, to: string): Promise<{ + productId: number; + name: string; + required: number; + unit: string; + available: number; + missing: number; + status: "enough" | "missing" | "pantry"; + }[]>; + upsert(user: { + userId: number; + }, dto: CreateMealPlanEntryDto): Promise<{ + recipe: { + name: string; + id: number; + imageUrl: string | null; + servings: number | null; + ingredients: { + product: { + name: string; + canonicalName: string | null; + id: number; + } | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + note: string | null; + }[]; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + }>; + removeByDate(user: { + userId: number; + }, date: string): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + }>; +} diff --git a/backend/dist/meal-plan/meal-plan.controller.js b/backend/dist/meal-plan/meal-plan.controller.js new file mode 100644 index 00000000..75399732 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.controller.js @@ -0,0 +1,88 @@ +"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.MealPlanController = void 0; +const common_1 = require("@nestjs/common"); +const meal_plan_service_1 = require("./meal-plan.service"); +const create_meal_plan_entry_dto_1 = require("./dto/create-meal-plan-entry.dto"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +let MealPlanController = class MealPlanController { + constructor(mealPlanService) { + this.mealPlanService = mealPlanService; + } + findByRange(user, from, to) { + return this.mealPlanService.findByRange(user.userId, from, to); + } + shoppingList(user, from, to) { + return this.mealPlanService.shoppingList(user.userId, from, to); + } + inventoryCompare(user, from, to) { + return this.mealPlanService.inventoryCompare(user.userId, from, to); + } + upsert(user, dto) { + return this.mealPlanService.upsert(user.userId, dto); + } + removeByDate(user, date) { + return this.mealPlanService.removeByDate(user.userId, date); + } +}; +exports.MealPlanController = MealPlanController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Query)('from')), + __param(2, (0, common_1.Query)('to')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], MealPlanController.prototype, "findByRange", null); +__decorate([ + (0, common_1.Get)('shopping-list'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Query)('from')), + __param(2, (0, common_1.Query)('to')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], MealPlanController.prototype, "shoppingList", null); +__decorate([ + (0, common_1.Get)('inventory-compare'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Query)('from')), + __param(2, (0, common_1.Query)('to')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], MealPlanController.prototype, "inventoryCompare", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_meal_plan_entry_dto_1.CreateMealPlanEntryDto]), + __metadata("design:returntype", void 0) +], MealPlanController.prototype, "upsert", null); +__decorate([ + (0, common_1.Delete)(':date'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Param)('date')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], MealPlanController.prototype, "removeByDate", null); +exports.MealPlanController = MealPlanController = __decorate([ + (0, common_1.Controller)('meal-plan'), + __metadata("design:paramtypes", [meal_plan_service_1.MealPlanService]) +], MealPlanController); +//# sourceMappingURL=meal-plan.controller.js.map \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.controller.js.map b/backend/dist/meal-plan/meal-plan.controller.js.map new file mode 100644 index 00000000..ac513b96 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"meal-plan.controller.js","sourceRoot":"","sources":["../../src/meal-plan/meal-plan.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmF;AACnF,2DAAsD;AACtD,iFAA0E;AAC1E,sFAAwE;AAGjE,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YAA6B,eAAgC;QAAhC,oBAAe,GAAf,eAAe,CAAiB;IAAG,CAAC;IAGjE,WAAW,CACM,IAAwB,EACxB,IAAY,EACd,EAAU;QAEvB,OAAO,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC;IAGD,YAAY,CACK,IAAwB,EACxB,IAAY,EACd,EAAU;QAEvB,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC;IAGD,gBAAgB,CACC,IAAwB,EACxB,IAAY,EACd,EAAU;QAEvB,OAAO,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC;IAGD,MAAM,CACW,IAAwB,EAC/B,GAA2B;QAEnC,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAGD,YAAY,CACK,IAAwB,EACxB,IAAY;QAE3B,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9D,CAAC;CACF,CAAA;AA7CY,gDAAkB;AAI7B;IADC,IAAA,YAAG,GAAE;IAEH,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,MAAM,CAAC,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;qDAGb;AAGD;IADC,IAAA,YAAG,EAAC,eAAe,CAAC;IAElB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,MAAM,CAAC,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;sDAGb;AAGD;IADC,IAAA,YAAG,EAAC,mBAAmB,CAAC;IAEtB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,MAAM,CAAC,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;0DAGb;AAGD;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,mDAAsB;;gDAGpC;AAGD;IADC,IAAA,eAAM,EAAC,OAAO,CAAC;IAEb,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,MAAM,CAAC,CAAA;;;;sDAGf;6BA5CU,kBAAkB;IAD9B,IAAA,mBAAU,EAAC,WAAW,CAAC;qCAEwB,mCAAe;GADlD,kBAAkB,CA6C9B"} \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.module.d.ts b/backend/dist/meal-plan/meal-plan.module.d.ts new file mode 100644 index 00000000..68fd4c75 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.module.d.ts @@ -0,0 +1,2 @@ +export declare class MealPlanModule { +} diff --git a/backend/dist/meal-plan/meal-plan.module.js b/backend/dist/meal-plan/meal-plan.module.js new file mode 100644 index 00000000..0fd490fb --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.module.js @@ -0,0 +1,24 @@ +"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.MealPlanModule = void 0; +const common_1 = require("@nestjs/common"); +const meal_plan_controller_1 = require("./meal-plan.controller"); +const meal_plan_service_1 = require("./meal-plan.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let MealPlanModule = class MealPlanModule { +}; +exports.MealPlanModule = MealPlanModule; +exports.MealPlanModule = MealPlanModule = __decorate([ + (0, common_1.Module)({ + controllers: [meal_plan_controller_1.MealPlanController], + providers: [meal_plan_service_1.MealPlanService], + imports: [prisma_module_1.PrismaModule], + }) +], MealPlanModule); +//# sourceMappingURL=meal-plan.module.js.map \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.module.js.map b/backend/dist/meal-plan/meal-plan.module.js.map new file mode 100644 index 00000000..274e21b0 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"meal-plan.module.js","sourceRoot":"","sources":["../../src/meal-plan/meal-plan.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA4D;AAC5D,2DAAsD;AACtD,2DAAuD;AAOhD,IAAM,cAAc,GAApB,MAAM,cAAc;CAAG,CAAA;AAAjB,wCAAc;yBAAd,cAAc;IAL1B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,yCAAkB,CAAC;QACjC,SAAS,EAAE,CAAC,mCAAe,CAAC;QAC5B,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,cAAc,CAAG"} \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.service.d.ts b/backend/dist/meal-plan/meal-plan.service.d.ts new file mode 100644 index 00000000..d51e6dd5 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.service.d.ts @@ -0,0 +1,83 @@ +import { PrismaService } from '../prisma/prisma.service'; +import { CreateMealPlanEntryDto } from './dto/create-meal-plan-entry.dto'; +export declare class MealPlanService { + private readonly prisma; + constructor(prisma: PrismaService); + findByRange(userId: number, from: string, to: string): Promise<({ + recipe: { + name: string; + id: number; + imageUrl: string | null; + servings: number | null; + ingredients: { + product: { + name: string; + canonicalName: string | null; + id: number; + } | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + note: string | null; + }[]; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + })[]>; + upsert(userId: number, dto: CreateMealPlanEntryDto): Promise<{ + recipe: { + name: string; + id: number; + imageUrl: string | null; + servings: number | null; + ingredients: { + product: { + name: string; + canonicalName: string | null; + id: number; + } | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + note: string | null; + }[]; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + }>; + removeByDate(userId: number, date: string): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + servings: number | null; + userId: number; + recipeId: number; + date: Date; + }>; + private aggregateIngredients; + shoppingList(userId: number, from: string, to: string): Promise<{ + productId: number; + name: string; + quantity: number; + unit: string; + }[]>; + inventoryCompare(userId: number, from: string, to: string): Promise<{ + productId: number; + name: string; + required: number; + unit: string; + available: number; + missing: number; + status: "enough" | "missing" | "pantry"; + }[]>; +} diff --git a/backend/dist/meal-plan/meal-plan.service.js b/backend/dist/meal-plan/meal-plan.service.js new file mode 100644 index 00000000..d99e4495 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.service.js @@ -0,0 +1,162 @@ +"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.MealPlanService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const recipeSelect = { + id: true, + name: true, + imageUrl: true, + servings: true, + ingredients: { + select: { + quantity: true, + unit: true, + note: true, + product: { select: { id: true, name: true, canonicalName: true } }, + }, + }, +}; +let MealPlanService = class MealPlanService { + constructor(prisma) { + this.prisma = prisma; + } + async findByRange(userId, from, to) { + return this.prisma.mealPlanEntry.findMany({ + where: { + userId, + date: { gte: new Date(from), lte: new Date(to) }, + }, + include: { recipe: { select: recipeSelect } }, + orderBy: { date: 'asc' }, + }); + } + async upsert(userId, dto) { + const date = new Date(dto.date); + return this.prisma.mealPlanEntry.upsert({ + where: { + userId_date: { + userId, + date, + }, + }, + create: { + userId, + date, + recipeId: dto.recipeId, + servings: dto.servings ?? null, + }, + update: { recipeId: dto.recipeId, servings: dto.servings ?? null }, + include: { recipe: { select: recipeSelect } }, + }); + } + async removeByDate(userId, date) { + const entry = await this.prisma.mealPlanEntry.findUnique({ + where: { + userId_date: { + userId, + date: new Date(date), + }, + }, + }); + if (!entry) + throw new common_1.NotFoundException('Ingen matplanspost för detta datum'); + return this.prisma.mealPlanEntry.delete({ where: { id: entry.id } }); + } + aggregateIngredients(entries) { + const map = new Map(); + for (const entry of entries) { + const recipeServings = entry.recipe.servings; + const entryServings = entry.servings; + const scale = recipeServings && entryServings ? entryServings / recipeServings : 1; + for (const ing of entry.recipe.ingredients) { + if (!ing.product || !ing.unit) { + continue; + } + const key = `${ing.product.id}-${ing.unit}`; + const qty = Number(ing.quantity ?? 0) * scale; + const existing = map.get(key); + if (existing) { + existing.quantity += qty; + } + else { + map.set(key, { + productId: ing.product.id, + name: ing.product.canonicalName || ing.product.name, + quantity: qty, + unit: ing.unit, + }); + } + } + } + return Array.from(map.values()); + } + async shoppingList(userId, from, to) { + const entries = await this.findByRange(userId, from, to); + return this.aggregateIngredients(entries).sort((a, b) => a.name.localeCompare(b.name, 'sv')); + } + async inventoryCompare(userId, from, to) { + const entries = await this.findByRange(userId, from, to); + const pantryItems = await this.prisma.pantryItem.findMany({ + where: { userId }, + select: { productId: true }, + }); + const pantryProductIds = new Set(pantryItems.map((p) => p.productId)); + const aggregated = this.aggregateIngredients(entries).map((item) => ({ + productId: item.productId, + name: item.name, + required: item.quantity, + unit: item.unit, + })); + const result = await Promise.all(aggregated.map(async (item) => { + if (pantryProductIds.has(item.productId)) { + return { + productId: item.productId, + name: item.name, + required: item.required, + unit: item.unit, + available: item.required, + missing: 0, + status: 'pantry', + }; + } + const inventoryItems = await this.prisma.inventoryItem.findMany({ + where: { productId: item.productId }, + }); + const available = inventoryItems + .filter((i) => i.unit.trim().toLowerCase() === item.unit.trim().toLowerCase()) + .reduce((sum, i) => sum + Number(i.quantity), 0); + return { + productId: item.productId, + name: item.name, + required: item.required, + unit: item.unit, + available, + missing: Math.max(0, item.required - available), + status: (available >= item.required ? 'enough' : 'missing'), + }; + })); + const statusOrder = { missing: 0, enough: 1, pantry: 2 }; + return result.sort((a, b) => { + const diff = statusOrder[a.status] - statusOrder[b.status]; + if (diff !== 0) + return diff; + return a.name.localeCompare(b.name, 'sv'); + }); + } +}; +exports.MealPlanService = MealPlanService; +exports.MealPlanService = MealPlanService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], MealPlanService); +//# sourceMappingURL=meal-plan.service.js.map \ No newline at end of file diff --git a/backend/dist/meal-plan/meal-plan.service.js.map b/backend/dist/meal-plan/meal-plan.service.js.map new file mode 100644 index 00000000..76643549 --- /dev/null +++ b/backend/dist/meal-plan/meal-plan.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"meal-plan.service.js","sourceRoot":"","sources":["../../src/meal-plan/meal-plan.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/pantry/dto/create-pantry-item.dto.d.ts b/backend/dist/pantry/dto/create-pantry-item.dto.d.ts new file mode 100644 index 00000000..1a848f9a --- /dev/null +++ b/backend/dist/pantry/dto/create-pantry-item.dto.d.ts @@ -0,0 +1,3 @@ +export declare class CreatePantryItemDto { + productId: number; +} diff --git a/backend/dist/pantry/dto/create-pantry-item.dto.js b/backend/dist/pantry/dto/create-pantry-item.dto.js new file mode 100644 index 00000000..8c615e38 --- /dev/null +++ b/backend/dist/pantry/dto/create-pantry-item.dto.js @@ -0,0 +1,22 @@ +"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.CreatePantryItemDto = void 0; +const class_validator_1 = require("class-validator"); +class CreatePantryItemDto { +} +exports.CreatePantryItemDto = CreatePantryItemDto; +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsPositive)(), + __metadata("design:type", Number) +], CreatePantryItemDto.prototype, "productId", void 0); +//# sourceMappingURL=create-pantry-item.dto.js.map \ No newline at end of file diff --git a/backend/dist/pantry/dto/create-pantry-item.dto.js.map b/backend/dist/pantry/dto/create-pantry-item.dto.js.map new file mode 100644 index 00000000..8277f336 --- /dev/null +++ b/backend/dist/pantry/dto/create-pantry-item.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-pantry-item.dto.js","sourceRoot":"","sources":["../../../src/pantry/dto/create-pantry-item.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAoD;AAEpD,MAAa,mBAAmB;CAI/B;AAJD,kDAIC;AADC;IAFC,IAAA,uBAAK,GAAE;IACP,IAAA,4BAAU,GAAE;;sDACK"} \ No newline at end of file diff --git a/backend/dist/pantry/pantry.controller.d.ts b/backend/dist/pantry/pantry.controller.d.ts new file mode 100644 index 00000000..0ae50bf6 --- /dev/null +++ b/backend/dist/pantry/pantry.controller.d.ts @@ -0,0 +1,65 @@ +import { PantryService } from './pantry.service'; +import { CreatePantryItemDto } from './dto/create-pantry-item.dto'; +export declare class PantryController { + private readonly pantryService; + constructor(pantryService: PantryService); + findAll(user: { + userId: number; + }): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + })[]>; + create(user: { + userId: number; + }, body: CreatePantryItemDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + }>; + remove(user: { + userId: number; + }, id: number): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + }>; +} diff --git a/backend/dist/pantry/pantry.controller.js b/backend/dist/pantry/pantry.controller.js new file mode 100644 index 00000000..40fa3c13 --- /dev/null +++ b/backend/dist/pantry/pantry.controller.js @@ -0,0 +1,62 @@ +"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.PantryController = void 0; +const common_1 = require("@nestjs/common"); +const pantry_service_1 = require("./pantry.service"); +const create_pantry_item_dto_1 = require("./dto/create-pantry-item.dto"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +let PantryController = class PantryController { + constructor(pantryService) { + this.pantryService = pantryService; + } + findAll(user) { + return this.pantryService.findAll(user.userId); + } + create(user, body) { + return this.pantryService.create(user.userId, body); + } + remove(user, id) { + return this.pantryService.remove(user.userId, id); + } +}; +exports.PantryController = PantryController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], PantryController.prototype, "findAll", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_pantry_item_dto_1.CreatePantryItemDto]), + __metadata("design:returntype", void 0) +], PantryController.prototype, "create", null); +__decorate([ + (0, common_1.Delete)(':id'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Number]), + __metadata("design:returntype", void 0) +], PantryController.prototype, "remove", null); +exports.PantryController = PantryController = __decorate([ + (0, common_1.Controller)('pantry'), + __metadata("design:paramtypes", [pantry_service_1.PantryService]) +], PantryController); +//# sourceMappingURL=pantry.controller.js.map \ No newline at end of file diff --git a/backend/dist/pantry/pantry.controller.js.map b/backend/dist/pantry/pantry.controller.js.map new file mode 100644 index 00000000..8131e29e --- /dev/null +++ b/backend/dist/pantry/pantry.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pantry.controller.js","sourceRoot":"","sources":["../../src/pantry/pantry.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA0F;AAC1F,qDAAiD;AACjD,yEAAmE;AACnE,sFAAwE;AAGjE,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAG7D,OAAO,CAAgB,IAAwB;QAC7C,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAGD,MAAM,CACW,IAAwB,EAC/B,IAAyB;QAEjC,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IAGD,MAAM,CACW,IAAwB,EACZ,EAAU;QAErC,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;CACF,CAAA;AAvBY,4CAAgB;AAI3B;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;+CAErB;AAGD;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAO,4CAAmB;;8CAGlC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IAEX,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;;;;8CAG3B;2BAtBU,gBAAgB;IAD5B,IAAA,mBAAU,EAAC,QAAQ,CAAC;qCAEyB,8BAAa;GAD9C,gBAAgB,CAuB5B"} \ No newline at end of file diff --git a/backend/dist/pantry/pantry.module.d.ts b/backend/dist/pantry/pantry.module.d.ts new file mode 100644 index 00000000..65b1c6d5 --- /dev/null +++ b/backend/dist/pantry/pantry.module.d.ts @@ -0,0 +1,2 @@ +export declare class PantryModule { +} diff --git a/backend/dist/pantry/pantry.module.js b/backend/dist/pantry/pantry.module.js new file mode 100644 index 00000000..03e30263 --- /dev/null +++ b/backend/dist/pantry/pantry.module.js @@ -0,0 +1,24 @@ +"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.PantryModule = void 0; +const common_1 = require("@nestjs/common"); +const pantry_controller_1 = require("./pantry.controller"); +const pantry_service_1 = require("./pantry.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let PantryModule = class PantryModule { +}; +exports.PantryModule = PantryModule; +exports.PantryModule = PantryModule = __decorate([ + (0, common_1.Module)({ + controllers: [pantry_controller_1.PantryController], + providers: [pantry_service_1.PantryService], + imports: [prisma_module_1.PrismaModule], + }) +], PantryModule); +//# sourceMappingURL=pantry.module.js.map \ No newline at end of file diff --git a/backend/dist/pantry/pantry.module.js.map b/backend/dist/pantry/pantry.module.js.map new file mode 100644 index 00000000..696ae923 --- /dev/null +++ b/backend/dist/pantry/pantry.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pantry.module.js","sourceRoot":"","sources":["../../src/pantry/pantry.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AACjD,2DAAuD;AAOhD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/backend/dist/pantry/pantry.service.d.ts b/backend/dist/pantry/pantry.service.d.ts new file mode 100644 index 00000000..2d72206a --- /dev/null +++ b/backend/dist/pantry/pantry.service.d.ts @@ -0,0 +1,59 @@ +import { PrismaService } from '../prisma/prisma.service'; +import { CreatePantryItemDto } from './dto/create-pantry-item.dto'; +export declare class PantryService { + private readonly prisma; + constructor(prisma: PrismaService); + findAll(userId: number): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + })[]>; + create(userId: number, data: CreatePantryItemDto): Promise<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + }>; + remove(userId: number, id: number): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + productId: number; + userId: number; + }>; +} diff --git a/backend/dist/pantry/pantry.service.js b/backend/dist/pantry/pantry.service.js new file mode 100644 index 00000000..696e4c8a --- /dev/null +++ b/backend/dist/pantry/pantry.service.js @@ -0,0 +1,62 @@ +"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.PantryService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +let PantryService = class PantryService { + constructor(prisma) { + this.prisma = prisma; + } + findAll(userId) { + return this.prisma.pantryItem.findMany({ + where: { userId }, + include: { + product: true, + }, + orderBy: { + product: { name: 'asc' }, + }, + }); + } + async create(userId, data) { + const existing = await this.prisma.pantryItem.findUnique({ + where: { + userId_productId: { + userId, + productId: data.productId, + }, + }, + }); + if (existing) { + throw new common_1.ConflictException('Produkten finns redan i baslagret'); + } + return this.prisma.pantryItem.create({ + data: { userId, productId: data.productId }, + include: { product: true }, + }); + } + async remove(userId, id) { + const item = await this.prisma.pantryItem.findFirst({ + where: { id, userId }, + }); + if (!item) { + throw new common_1.NotFoundException(`PantryItem med id ${id} hittades inte`); + } + return this.prisma.pantryItem.delete({ where: { id } }); + } +}; +exports.PantryService = PantryService; +exports.PantryService = PantryService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], PantryService); +//# sourceMappingURL=pantry.service.js.map \ No newline at end of file diff --git a/backend/dist/pantry/pantry.service.js.map b/backend/dist/pantry/pantry.service.js.map new file mode 100644 index 00000000..972f1d3e --- /dev/null +++ b/backend/dist/pantry/pantry.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"pantry.service.js","sourceRoot":"","sources":["../../src/pantry/pantry.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAkF;AAClF,6DAAyD;AAIlD,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;YACrC,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,OAAO,EAAE;gBACP,OAAO,EAAE,IAAI;aACd;YACD,OAAO,EAAE;gBACP,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;aACzB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,IAAyB;QACpD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;YACvD,KAAK,EAAE;gBACL,gBAAgB,EAAE;oBAChB,MAAM;oBACN,SAAS,EAAE,IAAI,CAAC,SAAS;iBAC1B;aACF;SACF,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,0BAAiB,CAAC,mCAAmC,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;YAC3C,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,EAAU;QACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;YAClD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,0BAAiB,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAC;QACvE,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF,CAAA;AA9CY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,aAAa,CA8CzB"} \ No newline at end of file diff --git a/backend/dist/prisma/prisma.module.d.ts b/backend/dist/prisma/prisma.module.d.ts new file mode 100644 index 00000000..1cba5ae6 --- /dev/null +++ b/backend/dist/prisma/prisma.module.d.ts @@ -0,0 +1,2 @@ +export declare class PrismaModule { +} diff --git a/backend/dist/prisma/prisma.module.js b/backend/dist/prisma/prisma.module.js new file mode 100644 index 00000000..d3729fa1 --- /dev/null +++ b/backend/dist/prisma/prisma.module.js @@ -0,0 +1,22 @@ +"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.PrismaModule = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("./prisma.service"); +let PrismaModule = class PrismaModule { +}; +exports.PrismaModule = PrismaModule; +exports.PrismaModule = PrismaModule = __decorate([ + (0, common_1.Global)(), + (0, common_1.Module)({ + providers: [prisma_service_1.PrismaService], + exports: [prisma_service_1.PrismaService], + }) +], PrismaModule); +//# sourceMappingURL=prisma.module.js.map \ No newline at end of file diff --git a/backend/dist/prisma/prisma.module.js.map b/backend/dist/prisma/prisma.module.js.map new file mode 100644 index 00000000..b1aba81c --- /dev/null +++ b/backend/dist/prisma/prisma.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prisma.module.js","sourceRoot":"","sources":["../../src/prisma/prisma.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/backend/dist/prisma/prisma.service.d.ts b/backend/dist/prisma/prisma.service.d.ts new file mode 100644 index 00000000..ef29084f --- /dev/null +++ b/backend/dist/prisma/prisma.service.d.ts @@ -0,0 +1,8 @@ +import { OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; +export declare class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + private readonly logger; + constructor(); + onModuleInit(): Promise; + onModuleDestroy(): Promise; +} diff --git a/backend/dist/prisma/prisma.service.js b/backend/dist/prisma/prisma.service.js new file mode 100644 index 00000000..8337472a --- /dev/null +++ b/backend/dist/prisma/prisma.service.js @@ -0,0 +1,51 @@ +"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 PrismaService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +let PrismaService = PrismaService_1 = class PrismaService extends client_1.PrismaClient { + constructor() { + super(); + this.logger = new common_1.Logger(PrismaService_1.name); + } + async onModuleInit() { + const maxAttempts = 10; + const delayMs = 3000; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + this.logger.log(`Försöker ansluta till databasen (försök ${attempt}/${maxAttempts})...`); + await this.$connect(); + this.logger.log('Databasanslutning etablerad.'); + return; + } + catch (error) { + const message = error instanceof Error ? error.message : 'Okänt fel'; + this.logger.warn(`Databasanslutning misslyckades på försök ${attempt}/${maxAttempts}: ${message}`); + if (attempt === maxAttempts) { + this.logger.error('Kunde inte ansluta till databasen efter maximalt antal försök.'); + throw error; + } + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } + } + async onModuleDestroy() { + await this.$disconnect(); + } +}; +exports.PrismaService = PrismaService; +exports.PrismaService = PrismaService = PrismaService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", []) +], PrismaService); +//# sourceMappingURL=prisma.service.js.map \ No newline at end of file diff --git a/backend/dist/prisma/prisma.service.js.map b/backend/dist/prisma/prisma.service.js.map new file mode 100644 index 00000000..f21cb00d --- /dev/null +++ b/backend/dist/prisma/prisma.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prisma.service.js","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAmF;AACnF,2CAA8C;AAGvC,IAAM,aAAa,qBAAnB,MAAM,aACX,SAAQ,qBAAY;IAKpB;QACE,KAAK,EAAE,CAAC;QAHO,WAAM,GAAG,IAAI,eAAM,CAAC,eAAa,CAAC,IAAI,CAAC,CAAC;IAIzD,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,WAAW,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC;QAErB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACxD,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,2CAA2C,OAAO,IAAI,WAAW,MAAM,CACxE,CAAC;gBAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAEtB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;gBAEvD,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,4CAA4C,OAAO,IAAI,WAAW,KAAK,OAAO,EAAE,CACjF,CAAC;gBAEF,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;oBAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,gEAAgE,CACjE,CAAC;oBACF,MAAM,KAAK,CAAC;gBACd,CAAC;gBAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AA/CY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;;GACA,aAAa,CA+CzB"} \ No newline at end of file diff --git a/backend/dist/products/dto/ai-categorize-bulk.dto.d.ts b/backend/dist/products/dto/ai-categorize-bulk.dto.d.ts new file mode 100644 index 00000000..84601c7e --- /dev/null +++ b/backend/dist/products/dto/ai-categorize-bulk.dto.d.ts @@ -0,0 +1,3 @@ +export declare class AiCategorizeBulkDto { + productIds?: number[]; +} diff --git a/backend/dist/products/dto/ai-categorize-bulk.dto.js b/backend/dist/products/dto/ai-categorize-bulk.dto.js new file mode 100644 index 00000000..b23ab1a0 --- /dev/null +++ b/backend/dist/products/dto/ai-categorize-bulk.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/ai-categorize-bulk.dto.js.map b/backend/dist/products/dto/ai-categorize-bulk.dto.js.map new file mode 100644 index 00000000..bf407a95 --- /dev/null +++ b/backend/dist/products/dto/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"} \ No newline at end of file diff --git a/backend/dist/products/dto/bulk-update-products.dto.d.ts b/backend/dist/products/dto/bulk-update-products.dto.d.ts new file mode 100644 index 00000000..8b305bf2 --- /dev/null +++ b/backend/dist/products/dto/bulk-update-products.dto.d.ts @@ -0,0 +1,4 @@ +export declare class BulkUpdateProductsDto { + ids: number[]; + categoryId?: number | null; +} diff --git a/backend/dist/products/dto/bulk-update-products.dto.js b/backend/dist/products/dto/bulk-update-products.dto.js new file mode 100644 index 00000000..3624f50c --- /dev/null +++ b/backend/dist/products/dto/bulk-update-products.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/bulk-update-products.dto.js.map b/backend/dist/products/dto/bulk-update-products.dto.js.map new file mode 100644 index 00000000..d889d8aa --- /dev/null +++ b/backend/dist/products/dto/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"} \ No newline at end of file diff --git a/backend/dist/products/dto/create-product.dto.d.ts b/backend/dist/products/dto/create-product.dto.d.ts new file mode 100644 index 00000000..5f2a8304 --- /dev/null +++ b/backend/dist/products/dto/create-product.dto.d.ts @@ -0,0 +1,4 @@ +export declare class CreateProductDto { + name: string; + categoryId?: number; +} diff --git a/backend/dist/products/dto/create-product.dto.js b/backend/dist/products/dto/create-product.dto.js new file mode 100644 index 00000000..89819085 --- /dev/null +++ b/backend/dist/products/dto/create-product.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/create-product.dto.js.map b/backend/dist/products/dto/create-product.dto.js.map new file mode 100644 index 00000000..511ad4be --- /dev/null +++ b/backend/dist/products/dto/create-product.dto.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/dto/merge-products.dto.d.ts b/backend/dist/products/dto/merge-products.dto.d.ts new file mode 100644 index 00000000..edc3e8a3 --- /dev/null +++ b/backend/dist/products/dto/merge-products.dto.d.ts @@ -0,0 +1,4 @@ +export declare class MergeProductsDto { + sourceProductId: number; + targetProductId: number; +} diff --git a/backend/dist/products/dto/merge-products.dto.js b/backend/dist/products/dto/merge-products.dto.js new file mode 100644 index 00000000..102f64fd --- /dev/null +++ b/backend/dist/products/dto/merge-products.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/merge-products.dto.js.map b/backend/dist/products/dto/merge-products.dto.js.map new file mode 100644 index 00000000..65fa2060 --- /dev/null +++ b/backend/dist/products/dto/merge-products.dto.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/dto/set-product-status.dto.d.ts b/backend/dist/products/dto/set-product-status.dto.d.ts new file mode 100644 index 00000000..d3765bed --- /dev/null +++ b/backend/dist/products/dto/set-product-status.dto.d.ts @@ -0,0 +1,3 @@ +export declare class SetProductStatusDto { + status: string; +} diff --git a/backend/dist/products/dto/set-product-status.dto.js b/backend/dist/products/dto/set-product-status.dto.js new file mode 100644 index 00000000..6b35cbaa --- /dev/null +++ b/backend/dist/products/dto/set-product-status.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/set-product-status.dto.js.map b/backend/dist/products/dto/set-product-status.dto.js.map new file mode 100644 index 00000000..50639c07 --- /dev/null +++ b/backend/dist/products/dto/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"} \ No newline at end of file diff --git a/backend/dist/products/dto/set-tags.dto.d.ts b/backend/dist/products/dto/set-tags.dto.d.ts new file mode 100644 index 00000000..916edc36 --- /dev/null +++ b/backend/dist/products/dto/set-tags.dto.d.ts @@ -0,0 +1,3 @@ +export declare class SetTagsDto { + tags: string[]; +} diff --git a/backend/dist/products/dto/set-tags.dto.js b/backend/dist/products/dto/set-tags.dto.js new file mode 100644 index 00000000..a44da7b4 --- /dev/null +++ b/backend/dist/products/dto/set-tags.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/set-tags.dto.js.map b/backend/dist/products/dto/set-tags.dto.js.map new file mode 100644 index 00000000..6ba70d2e --- /dev/null +++ b/backend/dist/products/dto/set-tags.dto.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/dto/update-canonical-name.dto.d.ts b/backend/dist/products/dto/update-canonical-name.dto.d.ts new file mode 100644 index 00000000..c988f8f0 --- /dev/null +++ b/backend/dist/products/dto/update-canonical-name.dto.d.ts @@ -0,0 +1,3 @@ +export declare class UpdateCanonicalNameDto { + canonicalName: string; +} diff --git a/backend/dist/products/dto/update-canonical-name.dto.js b/backend/dist/products/dto/update-canonical-name.dto.js new file mode 100644 index 00000000..3be9a209 --- /dev/null +++ b/backend/dist/products/dto/update-canonical-name.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/update-canonical-name.dto.js.map b/backend/dist/products/dto/update-canonical-name.dto.js.map new file mode 100644 index 00000000..1f0a49ef --- /dev/null +++ b/backend/dist/products/dto/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"} \ No newline at end of file diff --git a/backend/dist/products/dto/update-product.dto.d.ts b/backend/dist/products/dto/update-product.dto.d.ts new file mode 100644 index 00000000..2670cf2b --- /dev/null +++ b/backend/dist/products/dto/update-product.dto.d.ts @@ -0,0 +1,7 @@ +export declare class UpdateProductDto { + name?: string; + canonicalName?: string; + category?: string; + subcategory?: string; + categoryId?: number | null; +} diff --git a/backend/dist/products/dto/update-product.dto.js b/backend/dist/products/dto/update-product.dto.js new file mode 100644 index 00000000..5a7a26c4 --- /dev/null +++ b/backend/dist/products/dto/update-product.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/update-product.dto.js.map b/backend/dist/products/dto/update-product.dto.js.map new file mode 100644 index 00000000..0d71f83c --- /dev/null +++ b/backend/dist/products/dto/update-product.dto.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/dto/upsert-nutrition.dto.d.ts b/backend/dist/products/dto/upsert-nutrition.dto.d.ts new file mode 100644 index 00000000..2d9d54e6 --- /dev/null +++ b/backend/dist/products/dto/upsert-nutrition.dto.d.ts @@ -0,0 +1,9 @@ +export declare class UpsertNutritionDto { + calories?: number; + protein?: number; + fat?: number; + carbohydrates?: number; + salt?: number; + sugar?: number; + fiber?: number; +} diff --git a/backend/dist/products/dto/upsert-nutrition.dto.js b/backend/dist/products/dto/upsert-nutrition.dto.js new file mode 100644 index 00000000..3a65b20d --- /dev/null +++ b/backend/dist/products/dto/upsert-nutrition.dto.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/dto/upsert-nutrition.dto.js.map b/backend/dist/products/dto/upsert-nutrition.dto.js.map new file mode 100644 index 00000000..dc4f3c98 --- /dev/null +++ b/backend/dist/products/dto/upsert-nutrition.dto.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/products.controller.d.ts b/backend/dist/products/products.controller.d.ts new file mode 100644 index 00000000..b92e7979 --- /dev/null +++ b/backend/dist/products/products.controller.d.ts @@ -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; + 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; + }>; +} diff --git a/backend/dist/products/products.controller.js b/backend/dist/products/products.controller.js new file mode 100644 index 00000000..15fddad3 --- /dev/null +++ b/backend/dist/products/products.controller.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/products.controller.js.map b/backend/dist/products/products.controller.js.map new file mode 100644 index 00000000..6f76cadf --- /dev/null +++ b/backend/dist/products/products.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"products.controller.js","sourceRoot":"","sources":["../../src/products/products.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/products/products.module.d.ts b/backend/dist/products/products.module.d.ts new file mode 100644 index 00000000..08863074 --- /dev/null +++ b/backend/dist/products/products.module.d.ts @@ -0,0 +1,2 @@ +export declare class ProductsModule { +} diff --git a/backend/dist/products/products.module.js b/backend/dist/products/products.module.js new file mode 100644 index 00000000..180e1f21 --- /dev/null +++ b/backend/dist/products/products.module.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/products.module.js.map b/backend/dist/products/products.module.js.map new file mode 100644 index 00000000..764c8262 --- /dev/null +++ b/backend/dist/products/products.module.js.map @@ -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"} \ No newline at end of file diff --git a/backend/dist/products/products.service.d.ts b/backend/dist/products/products.service.d.ts new file mode 100644 index 00000000..49930d1c --- /dev/null +++ b/backend/dist/products/products.service.d.ts @@ -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>; +} diff --git a/backend/dist/products/products.service.js b/backend/dist/products/products.service.js new file mode 100644 index 00000000..2fe61bc8 --- /dev/null +++ b/backend/dist/products/products.service.js @@ -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 \ No newline at end of file diff --git a/backend/dist/products/products.service.js.map b/backend/dist/products/products.service.js.map new file mode 100644 index 00000000..552c63da --- /dev/null +++ b/backend/dist/products/products.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"products.service.js","sourceRoot":"","sources":["../../src/products/products.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/quick-import/dto/quick-import.dto.d.ts b/backend/dist/quick-import/dto/quick-import.dto.d.ts new file mode 100644 index 00000000..c65cbb5a --- /dev/null +++ b/backend/dist/quick-import/dto/quick-import.dto.d.ts @@ -0,0 +1,3 @@ +export declare class QuickImportDto { + input?: string; +} diff --git a/backend/dist/quick-import/dto/quick-import.dto.js b/backend/dist/quick-import/dto/quick-import.dto.js new file mode 100644 index 00000000..a5118775 --- /dev/null +++ b/backend/dist/quick-import/dto/quick-import.dto.js @@ -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.QuickImportDto = void 0; +const class_validator_1 = require("class-validator"); +class QuickImportDto { +} +exports.QuickImportDto = QuickImportDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.MaxLength)(2048), + __metadata("design:type", String) +], QuickImportDto.prototype, "input", void 0); +//# sourceMappingURL=quick-import.dto.js.map \ No newline at end of file diff --git a/backend/dist/quick-import/dto/quick-import.dto.js.map b/backend/dist/quick-import/dto/quick-import.dto.js.map new file mode 100644 index 00000000..9b84931e --- /dev/null +++ b/backend/dist/quick-import/dto/quick-import.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"quick-import.dto.js","sourceRoot":"","sources":["../../../src/quick-import/dto/quick-import.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAkE;AAElE,MAAa,cAAc;CAK1B;AALD,wCAKC;AADC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,IAAI,CAAC;;6CACD"} \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.controller.d.ts b/backend/dist/quick-import/quick-import.controller.d.ts new file mode 100644 index 00000000..e3c47e07 --- /dev/null +++ b/backend/dist/quick-import/quick-import.controller.d.ts @@ -0,0 +1,7 @@ +import { QuickImportDto } from './dto/quick-import.dto'; +import { QuickImportService, QuickImportResult } from './quick-import.service'; +export declare class QuickImportController { + private readonly quickImportService; + constructor(quickImportService: QuickImportService); + importFromInput(body: QuickImportDto, file?: Express.Multer.File): Promise; +} diff --git a/backend/dist/quick-import/quick-import.controller.js b/backend/dist/quick-import/quick-import.controller.js new file mode 100644 index 00000000..b3e8c632 --- /dev/null +++ b/backend/dist/quick-import/quick-import.controller.js @@ -0,0 +1,64 @@ +"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.QuickImportController = void 0; +const common_1 = require("@nestjs/common"); +const throttler_1 = require("@nestjs/throttler"); +const platform_express_1 = require("@nestjs/platform-express"); +const multer_1 = require("multer"); +const quick_import_dto_1 = require("./dto/quick-import.dto"); +const quick_import_service_1 = require("./quick-import.service"); +let QuickImportController = class QuickImportController { + constructor(quickImportService) { + this.quickImportService = quickImportService; + } + async importFromInput(body, file) { + if (file) { + return this.quickImportService.importFromUpload(file); + } + return this.quickImportService.importFromInput(body?.input ?? ''); + } +}; +exports.QuickImportController = QuickImportController; +__decorate([ + (0, common_1.Post)(), + (0, common_1.HttpCode)(200), + (0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 20 } }), + (0, common_1.UseInterceptors)((0, platform_express_1.FileInterceptor)('file', { + storage: (0, multer_1.memoryStorage)(), + limits: { fileSize: 10 * 1024 * 1024 }, + fileFilter: (req, file, callback) => { + if (file.mimetype === 'application/pdf' || + file.mimetype === 'application/octet-stream' || + file.mimetype === 'image/jpeg' || + file.mimetype === 'image/png' || + file.mimetype === 'image/webp') { + callback(null, true); + } + else { + callback(new Error('Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF.'), false); + } + }, + })), + __param(0, (0, common_1.Body)()), + __param(1, (0, common_1.UploadedFile)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [quick_import_dto_1.QuickImportDto, Object]), + __metadata("design:returntype", Promise) +], QuickImportController.prototype, "importFromInput", null); +exports.QuickImportController = QuickImportController = __decorate([ + (0, common_1.Controller)('quick-import'), + __metadata("design:paramtypes", [quick_import_service_1.QuickImportService]) +], QuickImportController); +//# sourceMappingURL=quick-import.controller.js.map \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.controller.js.map b/backend/dist/quick-import/quick-import.controller.js.map new file mode 100644 index 00000000..b55ee42b --- /dev/null +++ b/backend/dist/quick-import/quick-import.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"quick-import.controller.js","sourceRoot":"","sources":["../../src/quick-import/quick-import.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAiG;AACjG,iDAA6C;AAC7C,+DAA2D;AAC3D,mCAAuC;AACvC,6DAAwD;AACxD,iEAA+E;AAGxE,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,YAA6B,kBAAsC;QAAtC,uBAAkB,GAAlB,kBAAkB,CAAoB;IAAG,CAAC;IAwBjE,AAAN,KAAK,CAAC,eAAe,CACX,IAAoB,EACZ,IAA0B;QAE1C,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC;QAED,OAAO,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;CACF,CAAA;AAnCY,sDAAqB;AAyB1B;IAtBL,IAAA,aAAI,GAAE;IACN,IAAA,iBAAQ,EAAC,GAAG,CAAC;IACb,IAAA,oBAAQ,EAAC,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;IACjD,IAAA,wBAAe,EACd,IAAA,kCAAe,EAAC,MAAM,EAAE;QACtB,OAAO,EAAE,IAAA,sBAAa,GAAE;QACxB,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE;QACtC,UAAU,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;YAClC,IACE,IAAI,CAAC,QAAQ,KAAK,iBAAiB;gBACnC,IAAI,CAAC,QAAQ,KAAK,0BAA0B;gBAC5C,IAAI,CAAC,QAAQ,KAAK,YAAY;gBAC9B,IAAI,CAAC,QAAQ,KAAK,WAAW;gBAC7B,IAAI,CAAC,QAAQ,KAAK,YAAY,EAC9B,CAAC;gBACD,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,IAAI,KAAK,CAAC,qDAAqD,CAAC,EAAE,KAAK,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;KACF,CAAC,CACH;IAEE,WAAA,IAAA,aAAI,GAAE,CAAA;IACN,WAAA,IAAA,qBAAY,GAAE,CAAA;;qCADD,iCAAc;;4DAQ7B;gCAlCU,qBAAqB;IADjC,IAAA,mBAAU,EAAC,cAAc,CAAC;qCAEwB,yCAAkB;GADxD,qBAAqB,CAmCjC"} \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.module.d.ts b/backend/dist/quick-import/quick-import.module.d.ts new file mode 100644 index 00000000..a8a41472 --- /dev/null +++ b/backend/dist/quick-import/quick-import.module.d.ts @@ -0,0 +1,2 @@ +export declare class QuickImportModule { +} diff --git a/backend/dist/quick-import/quick-import.module.js b/backend/dist/quick-import/quick-import.module.js new file mode 100644 index 00000000..ed3bc83b --- /dev/null +++ b/backend/dist/quick-import/quick-import.module.js @@ -0,0 +1,22 @@ +"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.QuickImportModule = void 0; +const common_1 = require("@nestjs/common"); +const quick_import_controller_1 = require("./quick-import.controller"); +const quick_import_service_1 = require("./quick-import.service"); +let QuickImportModule = class QuickImportModule { +}; +exports.QuickImportModule = QuickImportModule; +exports.QuickImportModule = QuickImportModule = __decorate([ + (0, common_1.Module)({ + controllers: [quick_import_controller_1.QuickImportController], + providers: [quick_import_service_1.QuickImportService], + }) +], QuickImportModule); +//# sourceMappingURL=quick-import.module.js.map \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.module.js.map b/backend/dist/quick-import/quick-import.module.js.map new file mode 100644 index 00000000..0de6d577 --- /dev/null +++ b/backend/dist/quick-import/quick-import.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"quick-import.module.js","sourceRoot":"","sources":["../../src/quick-import/quick-import.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uEAAkE;AAClE,iEAA4D;AAMrD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAAG,CAAA;AAApB,8CAAiB;4BAAjB,iBAAiB;IAJ7B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,+CAAqB,CAAC;QACpC,SAAS,EAAE,CAAC,yCAAkB,CAAC;KAChC,CAAC;GACW,iBAAiB,CAAG"} \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.service.d.ts b/backend/dist/quick-import/quick-import.service.d.ts new file mode 100644 index 00000000..ade4453c --- /dev/null +++ b/backend/dist/quick-import/quick-import.service.d.ts @@ -0,0 +1,13 @@ +export interface QuickImportResult { + markdown: string; + source: 'ica' | 'pdf' | 'image' | 'other'; + imageUrl?: string; + imageWarning?: string; +} +export declare class QuickImportService { + private readonly logger; + importFromInput(input: string): Promise; + importFromUpload(file: Express.Multer.File): Promise; + private handleImporterResponse; + private downloadImageIfNeeded; +} diff --git a/backend/dist/quick-import/quick-import.service.js b/backend/dist/quick-import/quick-import.service.js new file mode 100644 index 00000000..20ec79a7 --- /dev/null +++ b/backend/dist/quick-import/quick-import.service.js @@ -0,0 +1,102 @@ +"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 QuickImportService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.QuickImportService = void 0; +const common_1 = require("@nestjs/common"); +const download_image_1 = require("../common/utils/download-image"); +const IMAGE_DEST_DIR = process.env.IMAGE_DEST_DIR || '/app/recipe-images'; +const IMPORTER_SERVICE_URL = process.env.IMPORTER_SERVICE_URL || 'http://importer-api:3001'; +let QuickImportService = QuickImportService_1 = class QuickImportService { + constructor() { + this.logger = new common_1.Logger(QuickImportService_1.name); + } + async importFromInput(input) { + const trimmed = input.trim(); + this.logger.log(`Delegerar URL-import till microservice: ${trimmed}`); + if (!trimmed) { + throw new common_1.BadRequestException('Du mÃ¥ste ange en URL eller ladda upp en fil'); + } + let response; + try { + response = await fetch(`${IMPORTER_SERVICE_URL}/api/quick-import`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ input: trimmed }), + }); + } + catch (err) { + this.logger.error(`Kunde inte nÃ¥ importer-api: ${err}`); + throw new common_1.ServiceUnavailableException('Import-tjänsten är inte tillgänglig. Försök igen senare.'); + } + const result = await this.handleImporterResponse(response); + return this.downloadImageIfNeeded(result); + } + async importFromUpload(file) { + this.logger.log(`Delegerar filuploading till microservice: ${file.originalname} (${file.mimetype})`); + const form = new FormData(); + form.append('file', new Blob([new Uint8Array(file.buffer)], { type: file.mimetype }), file.originalname); + let response; + try { + response = await fetch(`${IMPORTER_SERVICE_URL}/api/quick-import`, { + method: 'POST', + body: form, + }); + } + catch (err) { + this.logger.error(`Kunde inte nÃ¥ importer-api: ${err}`); + throw new common_1.ServiceUnavailableException('Import-tjänsten är inte tillgänglig. Försök igen senare.'); + } + const result = await this.handleImporterResponse(response); + return this.downloadImageIfNeeded(result); + } + async handleImporterResponse(response) { + if (!response.ok) { + let message = `Import-tjänsten svarade ${response.status}`; + try { + const body = (await response.json()); + if (body.message) + message = body.message; + } + catch { + } + this.logger.error(`Importer-api fel: ${message}`); + if (response.status >= 400 && response.status < 500) { + throw new common_1.BadRequestException(message); + } + throw new common_1.ServiceUnavailableException(message); + } + return response.json(); + } + async downloadImageIfNeeded(result) { + if (!result.imageUrl) + return result; + const imageUrl = result.imageUrl; + if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) { + return result; + } + this.logger.log(`Laddar ner receptbild: ${imageUrl}`); + try { + const localPath = await (0, download_image_1.downloadAndOptimizeImage)(imageUrl, IMAGE_DEST_DIR); + this.logger.log(`Bild sparad lokalt: ${localPath}`); + return { ...result, imageUrl: localPath }; + } + catch (imgErr) { + this.logger.warn(`Kunde inte ladda ner bild: ${imgErr} (källa: ${imageUrl})`); + return { + ...result, + imageWarning: result.imageWarning ?? 'Receptbild kunde inte laddas ner lokalt; extern URL används.', + }; + } + } +}; +exports.QuickImportService = QuickImportService; +exports.QuickImportService = QuickImportService = QuickImportService_1 = __decorate([ + (0, common_1.Injectable)() +], QuickImportService); +//# sourceMappingURL=quick-import.service.js.map \ No newline at end of file diff --git a/backend/dist/quick-import/quick-import.service.js.map b/backend/dist/quick-import/quick-import.service.js.map new file mode 100644 index 00000000..7a0704c4 --- /dev/null +++ b/backend/dist/quick-import/quick-import.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"quick-import.service.js","sourceRoot":"","sources":["../../src/quick-import/quick-import.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/receipt-alias/dto/create-receipt-alias.dto.d.ts b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.d.ts new file mode 100644 index 00000000..cb4d3dcb --- /dev/null +++ b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.d.ts @@ -0,0 +1,5 @@ +export declare class CreateReceiptAliasDto { + receiptName: string; + productId: number; + isGlobal?: boolean; +} diff --git a/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js new file mode 100644 index 00000000..e31dd5fa --- /dev/null +++ b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js @@ -0,0 +1,31 @@ +"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.CreateReceiptAliasDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateReceiptAliasDto { +} +exports.CreateReceiptAliasDto = CreateReceiptAliasDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(1), + __metadata("design:type", String) +], CreateReceiptAliasDto.prototype, "receiptName", void 0); +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateReceiptAliasDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], CreateReceiptAliasDto.prototype, "isGlobal", void 0); +//# sourceMappingURL=create-receipt-alias.dto.js.map \ No newline at end of file diff --git a/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js.map b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js.map new file mode 100644 index 00000000..dfdbc5c8 --- /dev/null +++ b/backend/dist/receipt-alias/dto/create-receipt-alias.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-receipt-alias.dto.js","sourceRoot":"","sources":["../../../src/receipt-alias/dto/create-receipt-alias.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAoF;AAEpF,MAAa,qBAAqB;CAWjC;AAXD,sDAWC;AARC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;0DACQ;AAGrB;IADC,IAAA,uBAAK,GAAE;;wDACW;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;uDACO"} \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.controller.d.ts b/backend/dist/receipt-alias/receipt-alias.controller.d.ts new file mode 100644 index 00000000..09414c0c --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.controller.d.ts @@ -0,0 +1,45 @@ +import { ReceiptAliasService } from './receipt-alias.service'; +import { CreateReceiptAliasDto } from './dto/create-receipt-alias.dto'; +export declare class ReceiptAliasController { + private readonly receiptAliasService; + constructor(receiptAliasService: ReceiptAliasService); + findAll(user: { + userId: number; + role: string; + }): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + name: string; + canonicalName: string | null; + id: number; + }; + } & { + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + })[]>; + upsert(dto: CreateReceiptAliasDto, user: { + userId: number; + role: string; + }): Promise<{ + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + }>; + remove(id: number, user: { + userId: number; + role: string; + }): Promise<{ + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + }>; +} diff --git a/backend/dist/receipt-alias/receipt-alias.controller.js b/backend/dist/receipt-alias/receipt-alias.controller.js new file mode 100644 index 00000000..2db1b9ce --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.controller.js @@ -0,0 +1,62 @@ +"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.ReceiptAliasController = void 0; +const common_1 = require("@nestjs/common"); +const receipt_alias_service_1 = require("./receipt-alias.service"); +const create_receipt_alias_dto_1 = require("./dto/create-receipt-alias.dto"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +let ReceiptAliasController = class ReceiptAliasController { + constructor(receiptAliasService) { + this.receiptAliasService = receiptAliasService; + } + findAll(user) { + return this.receiptAliasService.findAllForUser(user.userId, user.role); + } + upsert(dto, user) { + return this.receiptAliasService.upsert(dto, user.userId, user.role); + } + remove(id, user) { + return this.receiptAliasService.remove(id, user.userId, user.role); + } +}; +exports.ReceiptAliasController = ReceiptAliasController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ReceiptAliasController.prototype, "findAll", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [create_receipt_alias_dto_1.CreateReceiptAliasDto, Object]), + __metadata("design:returntype", void 0) +], ReceiptAliasController.prototype, "upsert", null); +__decorate([ + (0, common_1.Delete)(':id'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", void 0) +], ReceiptAliasController.prototype, "remove", null); +exports.ReceiptAliasController = ReceiptAliasController = __decorate([ + (0, common_1.Controller)('receipt-aliases'), + __metadata("design:paramtypes", [receipt_alias_service_1.ReceiptAliasService]) +], ReceiptAliasController); +//# sourceMappingURL=receipt-alias.controller.js.map \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.controller.js.map b/backend/dist/receipt-alias/receipt-alias.controller.js.map new file mode 100644 index 00000000..6285ae65 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-alias.controller.js","sourceRoot":"","sources":["../../src/receipt-alias/receipt-alias.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA0F;AAC1F,mEAA8D;AAC9D,6EAAuE;AACvE,sFAAwE;AAGjE,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IACjC,YAA6B,mBAAwC;QAAxC,wBAAmB,GAAnB,mBAAmB,CAAqB;IAAG,CAAC;IAGzE,OAAO,CAAgB,IAAsC;QAC3D,OAAO,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAGD,MAAM,CACI,GAA0B,EACnB,IAAsC;QAErD,OAAO,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC;IAGD,MAAM,CACuB,EAAU,EACtB,IAAsC;QAErD,OAAO,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;CACF,CAAA;AAvBY,wDAAsB;AAIjC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;qDAErB;AAGD;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,aAAI,GAAE,CAAA;IACN,WAAA,IAAA,oCAAW,GAAE,CAAA;;qCADD,gDAAqB;;oDAInC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IAEX,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;IACzB,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;oDAGf;iCAtBU,sBAAsB;IADlC,IAAA,mBAAU,EAAC,iBAAiB,CAAC;qCAEsB,2CAAmB;GAD1D,sBAAsB,CAuBlC"} \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.module.d.ts b/backend/dist/receipt-alias/receipt-alias.module.d.ts new file mode 100644 index 00000000..f8b56474 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.module.d.ts @@ -0,0 +1,2 @@ +export declare class ReceiptAliasModule { +} diff --git a/backend/dist/receipt-alias/receipt-alias.module.js b/backend/dist/receipt-alias/receipt-alias.module.js new file mode 100644 index 00000000..003161a8 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.module.js @@ -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.ReceiptAliasModule = void 0; +const common_1 = require("@nestjs/common"); +const receipt_alias_controller_1 = require("./receipt-alias.controller"); +const receipt_alias_service_1 = require("./receipt-alias.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let ReceiptAliasModule = class ReceiptAliasModule { +}; +exports.ReceiptAliasModule = ReceiptAliasModule; +exports.ReceiptAliasModule = ReceiptAliasModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [receipt_alias_controller_1.ReceiptAliasController], + providers: [receipt_alias_service_1.ReceiptAliasService], + exports: [receipt_alias_service_1.ReceiptAliasService], + }) +], ReceiptAliasModule); +//# sourceMappingURL=receipt-alias.module.js.map \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.module.js.map b/backend/dist/receipt-alias/receipt-alias.module.js.map new file mode 100644 index 00000000..be9da786 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-alias.module.js","sourceRoot":"","sources":["../../src/receipt-alias/receipt-alias.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yEAAoE;AACpE,mEAA8D;AAC9D,2DAAuD;AAQhD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAN9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,iDAAsB,CAAC;QACrC,SAAS,EAAE,CAAC,2CAAmB,CAAC;QAChC,OAAO,EAAE,CAAC,2CAAmB,CAAC;KAC/B,CAAC;GACW,kBAAkB,CAAG"} \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.service.d.ts b/backend/dist/receipt-alias/receipt-alias.service.d.ts new file mode 100644 index 00000000..35c0689e --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.service.d.ts @@ -0,0 +1,37 @@ +import { PrismaService } from '../prisma/prisma.service'; +import { CreateReceiptAliasDto } from './dto/create-receipt-alias.dto'; +export declare class ReceiptAliasService { + private readonly prisma; + constructor(prisma: PrismaService); + findAllForUser(userId: number, role: string): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + name: string; + canonicalName: string | null; + id: number; + }; + } & { + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + })[]>; + upsert(dto: CreateReceiptAliasDto, userId: number, role: string): Promise<{ + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + }>; + private upsertAliasRecord; + remove(id: number, userId: number, role: string): Promise<{ + id: number; + createdAt: Date; + ownerId: number | null; + productId: number; + receiptName: string; + isGlobal: boolean; + }>; +} diff --git a/backend/dist/receipt-alias/receipt-alias.service.js b/backend/dist/receipt-alias/receipt-alias.service.js new file mode 100644 index 00000000..2b535c40 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.service.js @@ -0,0 +1,76 @@ +"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.ReceiptAliasService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +let ReceiptAliasService = class ReceiptAliasService { + constructor(prisma) { + this.prisma = prisma; + } + findAllForUser(userId, role) { + const where = role === 'admin' + ? undefined + : { + OR: [ + { ownerId: userId, isGlobal: false }, + { isGlobal: true }, + ], + }; + return this.prisma.receiptAlias.findMany({ + where, + include: { product: { select: { id: true, name: true, canonicalName: true } } }, + orderBy: { receiptName: 'asc' }, + }); + } + async upsert(dto, userId, role) { + const normalized = dto.receiptName.toLowerCase().trim(); + const wantsGlobal = dto.isGlobal === true; + if (wantsGlobal && role !== 'admin') { + throw new common_1.ForbiddenException('Endast admin kan skapa globala alias'); + } + return this.upsertAliasRecord(normalized, dto.productId, wantsGlobal ? null : userId, wantsGlobal); + } + async upsertAliasRecord(receiptName, productId, ownerId, isGlobal) { + const existing = await this.prisma.receiptAlias.findFirst({ + where: isGlobal + ? { receiptName, isGlobal: true } + : { receiptName, ownerId, isGlobal: false }, + }); + if (existing) { + return this.prisma.receiptAlias.update({ + where: { id: existing.id }, + data: { productId }, + }); + } + return this.prisma.receiptAlias.create({ + data: { receiptName, productId, ownerId, isGlobal }, + }); + } + async remove(id, userId, role) { + const alias = await this.prisma.receiptAlias.findUnique({ where: { id } }); + if (!alias) { + throw new common_1.NotFoundException(`Aliaspost med id ${id} hittades inte`); + } + const canDelete = role === 'admin' || + (alias.ownerId === userId && alias.isGlobal === false); + if (!canDelete) { + throw new common_1.ForbiddenException('Du har inte behörighet att ta bort aliaset'); + } + return this.prisma.receiptAlias.delete({ where: { id } }); + } +}; +exports.ReceiptAliasService = ReceiptAliasService; +exports.ReceiptAliasService = ReceiptAliasService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ReceiptAliasService); +//# sourceMappingURL=receipt-alias.service.js.map \ No newline at end of file diff --git a/backend/dist/receipt-alias/receipt-alias.service.js.map b/backend/dist/receipt-alias/receipt-alias.service.js.map new file mode 100644 index 00000000..35c95075 --- /dev/null +++ b/backend/dist/receipt-alias/receipt-alias.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-alias.service.js","sourceRoot":"","sources":["../../src/receipt-alias/receipt-alias.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/receipt-import/dto/parsed-receipt-item.dto.d.ts b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.d.ts new file mode 100644 index 00000000..eddf1339 --- /dev/null +++ b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.d.ts @@ -0,0 +1,14 @@ +import type { CategorySuggestion } from '../../ai/ai.service'; +export interface ParsedReceiptItem { + rawName: string; + quantity: number; + unit: string; + price?: number | null; + brand?: string | null; + origin?: string | null; + matchedProductId?: number; + matchedProductName?: string; + suggestedProductId?: number; + suggestedProductName?: string; + categorySuggestion?: CategorySuggestion; +} diff --git a/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js new file mode 100644 index 00000000..f952806b --- /dev/null +++ b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=parsed-receipt-item.dto.js.map \ No newline at end of file diff --git a/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js.map b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js.map new file mode 100644 index 00000000..563d09e0 --- /dev/null +++ b/backend/dist/receipt-import/dto/parsed-receipt-item.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parsed-receipt-item.dto.js","sourceRoot":"","sources":["../../../src/receipt-import/dto/parsed-receipt-item.dto.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.controller.d.ts b/backend/dist/receipt-import/receipt-import.controller.d.ts new file mode 100644 index 00000000..afedc708 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.controller.d.ts @@ -0,0 +1,10 @@ +import { ReceiptImportService } from './receipt-import.service'; +import { ParsedReceiptItem } from './dto/parsed-receipt-item.dto'; +export declare class ReceiptImportController { + private readonly receiptImportService; + constructor(receiptImportService: ReceiptImportService); + parseReceipt(file?: Express.Multer.File, req?: any): Promise; + refreshCategories(): Promise<{ + message: string; + }>; +} diff --git a/backend/dist/receipt-import/receipt-import.controller.js b/backend/dist/receipt-import/receipt-import.controller.js new file mode 100644 index 00000000..5f8ca665 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.controller.js @@ -0,0 +1,77 @@ +"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.ReceiptImportController = void 0; +const common_1 = require("@nestjs/common"); +const throttler_1 = require("@nestjs/throttler"); +const platform_express_1 = require("@nestjs/platform-express"); +const multer_1 = require("multer"); +const receipt_import_service_1 = require("./receipt-import.service"); +const passport_1 = require("@nestjs/passport"); +const ALLOWED_MIMES = [ + 'image/jpeg', + 'image/png', + 'image/webp', + 'image/heic', + 'image/heif', + 'application/pdf', + 'application/octet-stream', +]; +let ReceiptImportController = class ReceiptImportController { + constructor(receiptImportService) { + this.receiptImportService = receiptImportService; + } + async parseReceipt(file, req) { + if (!file?.buffer) { + throw new common_1.BadRequestException('Ingen fil skickades med.'); + } + if (!ALLOWED_MIMES.includes(file.mimetype)) { + throw new common_1.BadRequestException('Otillåten filtyp. Använd JPEG, PNG, WebP eller PDF.'); + } + const isPremium = req?.user?.isPremium === true || req?.user?.role === 'admin'; + const userId = typeof req?.user?.id === 'number' ? req.user.id : undefined; + return this.receiptImportService.parseReceipt(file, isPremium, userId); + } + async refreshCategories() { + await this.receiptImportService.loadCategories(); + return { message: 'Kategorier har uppdaterats.' }; + } +}; +exports.ReceiptImportController = ReceiptImportController; +__decorate([ + (0, common_1.HttpCode)(200), + (0, common_1.Post)(), + (0, throttler_1.Throttle)({ default: { ttl: 60_000, limit: 20 } }), + (0, common_1.UseInterceptors)((0, platform_express_1.FileInterceptor)('file', { + storage: (0, multer_1.memoryStorage)(), + limits: { fileSize: 15 * 1024 * 1024 }, + })), + __param(0, (0, common_1.UploadedFile)()), + __param(1, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], ReceiptImportController.prototype, "parseReceipt", null); +__decorate([ + (0, common_1.Post)('refresh-categories'), + (0, common_1.UseGuards)((0, passport_1.AuthGuard)('jwt')), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], ReceiptImportController.prototype, "refreshCategories", null); +exports.ReceiptImportController = ReceiptImportController = __decorate([ + (0, common_1.Controller)('receipt-import'), + __metadata("design:paramtypes", [receipt_import_service_1.ReceiptImportService]) +], ReceiptImportController); +//# sourceMappingURL=receipt-import.controller.js.map \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.controller.js.map b/backend/dist/receipt-import/receipt-import.controller.js.map new file mode 100644 index 00000000..f12eeb09 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-import.controller.js","sourceRoot":"","sources":["../../src/receipt-import/receipt-import.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,iDAA6C;AAC7C,+DAA2D;AAC3D,mCAAuC;AACvC,qEAAgE;AAEhE,+CAA6C;AAE7C,MAAM,aAAa,GAAG;IACpB,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,iBAAiB;IACjB,0BAA0B;CAC3B,CAAC;AAGK,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,YAA6B,oBAA0C;QAA1C,yBAAoB,GAApB,oBAAoB,CAAsB;IAAG,CAAC;IAWrE,AAAN,KAAK,CAAC,YAAY,CACA,IAA0B,EAC/B,GAAS;QAEpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,4BAAmB,CAAC,0BAA0B,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,4BAAmB,CAC3B,qDAAqD,CACtD,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;QAC/E,MAAM,MAAM,GAAG,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3E,OAAO,IAAI,CAAC,oBAAoB,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC;IAIK,AAAN,KAAK,CAAC,iBAAiB;QACrB,MAAM,IAAI,CAAC,oBAAoB,CAAC,cAAc,EAAE,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACpD,CAAC;CACF,CAAA;AAnCY,0DAAuB;AAY5B;IATL,IAAA,iBAAQ,EAAC,GAAG,CAAC;IACb,IAAA,aAAI,GAAE;IACN,IAAA,oBAAQ,EAAC,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;IACjD,IAAA,wBAAe,EACd,IAAA,kCAAe,EAAC,MAAM,EAAE;QACtB,OAAO,EAAE,IAAA,sBAAa,GAAE;QACxB,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE;KACvC,CAAC,CACH;IAEE,WAAA,IAAA,qBAAY,GAAE,CAAA;IACd,WAAA,IAAA,gBAAO,GAAE,CAAA;;;;2DAaX;AAIK;IAFL,IAAA,aAAI,EAAC,oBAAoB,CAAC;IAC1B,IAAA,kBAAS,EAAC,IAAA,oBAAS,EAAC,KAAK,CAAC,CAAC;;;;gEAI3B;kCAlCU,uBAAuB;IADnC,IAAA,mBAAU,EAAC,gBAAgB,CAAC;qCAEwB,6CAAoB;GAD5D,uBAAuB,CAmCnC"} \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.module.d.ts b/backend/dist/receipt-import/receipt-import.module.d.ts new file mode 100644 index 00000000..ef4f8488 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.module.d.ts @@ -0,0 +1,2 @@ +export declare class ReceiptImportModule { +} diff --git a/backend/dist/receipt-import/receipt-import.module.js b/backend/dist/receipt-import/receipt-import.module.js new file mode 100644 index 00000000..38e0e6a1 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.module.js @@ -0,0 +1,26 @@ +"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.ReceiptImportModule = void 0; +const common_1 = require("@nestjs/common"); +const receipt_import_controller_1 = require("./receipt-import.controller"); +const receipt_import_service_1 = require("./receipt-import.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +const ai_module_1 = require("../ai/ai.module"); +const categories_module_1 = require("../categories/categories.module"); +let ReceiptImportModule = class ReceiptImportModule { +}; +exports.ReceiptImportModule = ReceiptImportModule; +exports.ReceiptImportModule = ReceiptImportModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule, ai_module_1.AiModule, categories_module_1.CategoriesModule], + controllers: [receipt_import_controller_1.ReceiptImportController], + providers: [receipt_import_service_1.ReceiptImportService], + }) +], ReceiptImportModule); +//# sourceMappingURL=receipt-import.module.js.map \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.module.js.map b/backend/dist/receipt-import/receipt-import.module.js.map new file mode 100644 index 00000000..f42732fa --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-import.module.js","sourceRoot":"","sources":["../../src/receipt-import/receipt-import.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2EAAsE;AACtE,qEAAgE;AAChE,2DAAuD;AACvD,+CAA2C;AAC3C,uEAAmE;AAO5D,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAAG,CAAA;AAAtB,kDAAmB;8BAAnB,mBAAmB;IAL/B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,EAAE,oBAAQ,EAAE,oCAAgB,CAAC;QACnD,WAAW,EAAE,CAAC,mDAAuB,CAAC;QACtC,SAAS,EAAE,CAAC,6CAAoB,CAAC;KAClC,CAAC;GACW,mBAAmB,CAAG"} \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.service.d.ts b/backend/dist/receipt-import/receipt-import.service.d.ts new file mode 100644 index 00000000..98b61a1c --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.d.ts @@ -0,0 +1,25 @@ +import { PrismaService } from '../prisma/prisma.service'; +import { ParsedReceiptItem } from './dto/parsed-receipt-item.dto'; +import { AiService } from '../ai/ai.service'; +import { CategoriesService } from '../categories/categories.service'; +export declare function isIgnoredReceiptName(value: string | null | undefined): boolean; +export declare class ReceiptImportService { + private readonly prisma; + private readonly aiService; + private readonly categoriesService; + private readonly logger; + private cachedCategories; + constructor(prisma: PrismaService, aiService: AiService, categoriesService: CategoriesService); + loadCategories(): Promise; + parseReceipt(file: Express.Multer.File, _isPremium?: boolean, userId?: number): Promise; + private parseReceiptViaImporter; + private matchProducts; + private findWordMatch; + private enrichWithAiCategories; + private shouldTraceDecision; + private resolvePorkCategory; + private resolveBreadCategory; + private applyHardCategoryOverrides; + private ruleBasedCategorySuggestion; + private applyContradictionGuard; +} diff --git a/backend/dist/receipt-import/receipt-import.service.js b/backend/dist/receipt-import/receipt-import.service.js new file mode 100644 index 00000000..da45074c --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.js @@ -0,0 +1,983 @@ +"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 ReceiptImportService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ReceiptImportService = void 0; +exports.isIgnoredReceiptName = isIgnoredReceiptName; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const ai_service_1 = require("../ai/ai.service"); +const categories_service_1 = require("../categories/categories.service"); +const IMPORTER_SERVICE_URL = process.env.IMPORTER_SERVICE_URL || 'http://importer-api:3001'; +const WEAK_DESCRIPTORS = new Set([ + 'rokt', + 'rökt', + 'kokt', + 'grillad', + 'stekt', + 'skivad', + 'strimlad', + 'fryst', + 'farsk', + 'färsk', +]); +function tokenize(value) { + return value + .toLowerCase() + .split(/[^a-z0-9åäö]+/) + .filter((w) => w.length >= 3); +} +function isIgnoredReceiptName(value) { + const normalized = (value ?? '').trim().toLowerCase(); + if (!normalized) + return false; + if (/^rabatt\b/.test(normalized)) + return true; + if (/^summa\b/.test(normalized)) + return true; + if (/^moms\b/.test(normalized)) + return true; + if (/^pant\b/.test(normalized)) + return true; + if (/^att\s+betala\b/.test(normalized)) + return true; + if (/^totalt\b/.test(normalized)) + return true; + if (/^kort\b/.test(normalized)) + return true; + if (/^kontant\b/.test(normalized)) + return true; + if (/^willys\s+plus\s*[:\-]?\b/.test(normalized)) + return true; + return false; +} +function normalizeToken(s) { + return s.replace(/å/g, 'a').replace(/ä/g, 'a').replace(/ö/g, 'o').replace(/é/g, 'e').replace(/è/g, 'e'); +} +function normalizeForRules(value) { + return value + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-z0-9]+/g, ' ') + .trim(); +} +function hasPorkLikeSignal(normalized) { + return (normalized.includes('bacon') || + normalized.includes('bacn') || + normalized.includes('baco') || + /\bbac[a-z0-9]{1,5}\b/.test(normalized) || + /\bsidflask\b/.test(normalized) || + /\bpancetta\b/.test(normalized) || + /\bflask\b/.test(normalized) || + /\bflaskfile\b/.test(normalized) || + /\bkarr[eé]\b/.test(normalized) || + /\bkotlett\b/.test(normalized)); +} +function hasBreadLikeSignal(normalized) { + return (/\brostbrod\b/.test(normalized) || + /\brost\s*n\s*toast\b/.test(normalized) || + /\broast\s*n\s*toast\b/.test(normalized) || + /\btoastbrod\b/.test(normalized) || + /\bformbrod\b/.test(normalized) || + /\blantbrod\b/.test(normalized) || + /\bfullkornsbrod\b/.test(normalized) || + /\bfranska\b/.test(normalized) || + /\blimpa\b/.test(normalized) || + /\bbrod\b/.test(normalized) || + /\btoast\b/.test(normalized)); +} +function inferPackageDebugFromRawName(rawName) { + const normalized = rawName.toLowerCase(); + const multiPack = /(\d+)\s*[x×]\s*(\d+(?:[\.,]\d+)?)\s*(ml|cl|dl|l|g|kg)\b/i.exec(normalized); + if (multiPack) { + const count = Number.parseInt(multiPack[1], 10); + const qty = Number.parseFloat(multiPack[2].replace(',', '.')); + const unit = multiPack[3].toLowerCase(); + return { + packageCount: Number.isFinite(count) && count > 0 ? count : 1, + packQuantity: Number.isFinite(qty) ? qty : null, + packUnit: unit, + }; + } + const singlePack = /(\d+(?:[\.,]\d+)?)\s*(ml|cl|dl|l|g|kg)\b/i.exec(normalized.replace(/([\d.,]+)(ml|cl|dl|l|g|kg)\b/i, '$1 $2')); + if (singlePack) { + const qty = Number.parseFloat(singlePack[1].replace(',', '.')); + const unit = singlePack[2].toLowerCase(); + return { + packageCount: 1, + packQuantity: Number.isFinite(qty) ? qty : null, + packUnit: unit, + }; + } + return { + packageCount: 1, + packQuantity: null, + packUnit: null, + }; +} +let ReceiptImportService = ReceiptImportService_1 = class ReceiptImportService { + constructor(prisma, aiService, categoriesService) { + this.prisma = prisma; + this.aiService = aiService; + this.categoriesService = categoriesService; + this.logger = new common_1.Logger(ReceiptImportService_1.name); + this.cachedCategories = []; + this.loadCategories(); + } + async loadCategories() { + this.cachedCategories = await this.prisma.category.findMany({ + include: { children: true }, + }); + } + async parseReceipt(file, _isPremium = false, userId) { + const rawItems = await this.parseReceiptViaImporter(file); + const matched = await this.matchProducts(rawItems, userId); + return this.enrichWithAiCategories(matched); + } + async parseReceiptViaImporter(file) { + const form = new FormData(); + form.append('file', new Blob([new Uint8Array(file.buffer)], { type: file.mimetype }), file.originalname); + let response; + try { + response = await fetch(`${IMPORTER_SERVICE_URL}/api/receipt-import/parse`, { + method: 'POST', + body: form, + }); + } + catch (err) { + this.logger.error(`Kunde inte nå importer-api för kvittoparsning: ${err}`); + throw new common_1.ServiceUnavailableException('Import-tjänsten är inte tillgänglig. Försök igen senare.'); + } + if (!response.ok) { + let message = `Importer svarade ${response.status}`; + try { + const body = (await response.json()); + if (body.message) + message = body.message; + } + catch { + } + if (response.status === 503 || response.status === 429) { + throw new common_1.ServiceUnavailableException(message); + } + throw new common_1.BadRequestException(message); + } + const items = (await response.json()); + return items.filter((item) => !isIgnoredReceiptName(item.rawName)); + } + async matchProducts(items, userId) { + const productFilter = userId ? { isActive: true, ownerId: userId } : { isActive: true }; + const aliasFilter = userId + ? { + OR: [ + { ownerId: userId, isGlobal: false }, + { isGlobal: true }, + ], + } + : { isGlobal: true }; + const [aliases, products] = await Promise.all([ + this.prisma.receiptAlias.findMany({ + where: aliasFilter, + orderBy: [ + { isGlobal: 'asc' }, + { id: 'asc' }, + ], + select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } } } }, + }), + this.prisma.product.findMany({ + where: productFilter, + select: { id: true, name: true, canonicalName: true, categoryId: true, categoryRef: { select: { id: true, name: true } } }, + }), + ]); + return items.map((item) => { + const raw = (item.rawName ?? '').toLowerCase().trim(); + if (!raw) + return item; + const alias = aliases.find((a) => a.receiptName === raw); + if (alias) { + const cat = alias.product.categoryRef; + return { + ...item, + matchedProductId: alias.product.id, + matchedProductName: alias.product.canonicalName ?? alias.product.name, + ...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'high', usedFallback: false } } : {}), + }; + } + const suggestion = this.findWordMatch(raw, products); + if (!suggestion) { + return { ...item }; + } + const cat = suggestion.categoryRef; + return { + ...item, + suggestedProductId: suggestion.id, + suggestedProductName: suggestion.canonicalName ?? suggestion.name, + ...(cat ? { categorySuggestion: { categoryId: cat.id, categoryName: cat.name, path: cat.name, confidence: 'medium', usedFallback: false } } : {}), + }; + }); + } + findWordMatch(raw, products) { + const rawWords = tokenize(raw); + if (rawWords.length === 0) + return undefined; + const rawWordSet = new Set(rawWords); + const rawWordsNorm = rawWords.map(normalizeToken); + const rawWordSetNorm = new Set(rawWordsNorm); + let best; + for (const product of products) { + const productWords = tokenize(product.canonicalName ?? product.name); + if (productWords.length === 0) + continue; + let score = 0; + let exactStrong = 0; + let exactAny = 0; + let partialStrong = 0; + const phrase = (product.canonicalName ?? product.name).toLowerCase(); + if (raw.includes(phrase)) { + score += 5; + } + for (const pw of productWords) { + const isWeak = WEAK_DESCRIPTORS.has(pw); + const pwNorm = normalizeToken(pw); + if (rawWordSet.has(pw) || rawWordSetNorm.has(pwNorm)) { + exactAny += 1; + if (isWeak) { + score += 1; + } + else { + exactStrong += 1; + score += 8; + } + continue; + } + if (pw.length < 4) + continue; + const hasPartial = rawWords.some((rw) => rw.includes(pw) || pw.includes(rw)) || + rawWordsNorm.some((rw) => rw.includes(pwNorm) || pwNorm.includes(rw)); + if (!hasPartial) + continue; + if (isWeak) { + continue; + } + partialStrong += 1; + score += 3; + } + const hasLongPartial = partialStrong >= 1 && productWords.some((pw) => pw.length >= 5); + const hasStrongSignal = exactStrong >= 1 || exactAny + partialStrong >= 2 || hasLongPartial; + if (!hasStrongSignal) + continue; + if (score < 8) + continue; + if (!best || score > best.score) { + best = { product, score }; + } + } + return best?.product; + } + async enrichWithAiCategories(items) { + let categories; + try { + categories = await this.categoriesService.findFlattened(); + } + catch { + return items; + } + const enriched = []; + for (const item of items) { + if (!item.rawName) { + enriched.push(item); + continue; + } + try { + const signalText = [ + item.rawName, + item.matchedProductName, + item.suggestedProductName, + ] + .filter((v) => typeof v === 'string' && v.trim().length > 0) + .join(' '); + const trace = []; + const traceEnabled = this.shouldTraceDecision(signalText || item.rawName); + const pushTrace = (msg) => { + if (traceEnabled) + trace.push(msg); + }; + const pkg = inferPackageDebugFromRawName(item.rawName); + pushTrace(`start raw="${item.rawName}" signal="${signalText || item.rawName}" parsedQuantity=${item.quantity ?? 'null'} parsedUnit=${item.unit ?? 'null'} packageCount=${pkg.packageCount} packQuantity=${pkg.packQuantity ?? 'null'} packUnit=${pkg.packUnit ?? 'null'}`); + pushTrace(`match matchedProductId=${item.matchedProductId ?? 'null'} suggestedProductId=${item.suggestedProductId ?? 'null'}`); + if (item.categorySuggestion) { + pushTrace(`incoming category="${item.categorySuggestion.path}" confidence=${item.categorySuggestion.confidence} fallback=${item.categorySuggestion.usedFallback}`); + } + const byRule = this.ruleBasedCategorySuggestion(signalText || item.rawName, categories); + if (byRule) { + pushTrace(`rule hit -> "${byRule.path}" (${byRule.confidence})`); + } + else { + pushTrace('rule miss'); + } + let nextSuggestion = item.categorySuggestion ?? null; + const isTrustedSuggestion = nextSuggestion?.confidence === 'high' && !nextSuggestion.usedFallback; + if (byRule?.confidence === 'high') { + const sameAsCurrent = nextSuggestion != null && nextSuggestion.categoryId === byRule.categoryId; + if (sameAsCurrent && nextSuggestion && nextSuggestion.confidence !== 'high') { + nextSuggestion = { ...nextSuggestion, confidence: 'high' }; + pushTrace(`rule applied -> "${byRule.path}" (confidence upgraded to high)`); + } + else if (!sameAsCurrent && (!isTrustedSuggestion || nextSuggestion == null)) { + nextSuggestion = byRule; + pushTrace(`rule applied -> "${byRule.path}"`); + } + if (!sameAsCurrent && isTrustedSuggestion) { + this.logger.log(`Rule-override: "${item.rawName}" ändras från "${nextSuggestion?.path}" till "${byRule.path}"`); + nextSuggestion = byRule; + pushTrace(`rule override trusted -> "${byRule.path}"`); + } + } + else if (!nextSuggestion && byRule) { + nextSuggestion = byRule; + pushTrace(`rule fallback applied -> "${byRule.path}"`); + } + if (!nextSuggestion) { + pushTrace('ai invoked'); + nextSuggestion = await this.aiService.suggestCategory(item.rawName, categories); + pushTrace(`ai result -> "${nextSuggestion.path}" (${nextSuggestion.confidence})`); + } + else { + pushTrace(`ai skipped, current -> "${nextSuggestion.path}"`); + } + const beforeGuardPath = nextSuggestion?.path; + const guardedSuggestion = nextSuggestion + ? this.applyContradictionGuard(signalText || item.rawName, nextSuggestion, categories) + : null; + if (guardedSuggestion && beforeGuardPath !== guardedSuggestion.path) { + pushTrace(`contradiction guard remap "${beforeGuardPath}" -> "${guardedSuggestion.path}"`); + } + const beforeHardPath = guardedSuggestion?.path; + const finalSuggestion = guardedSuggestion + ? this.applyHardCategoryOverrides(signalText || item.rawName, guardedSuggestion, categories) + : null; + if (finalSuggestion && beforeHardPath !== finalSuggestion.path) { + pushTrace(`hard override remap "${beforeHardPath}" -> "${finalSuggestion.path}"`); + } + if (finalSuggestion) { + pushTrace(`final -> "${finalSuggestion.path}" (${finalSuggestion.confidence})`); + } + else { + pushTrace('final -> no categorySuggestion'); + } + if (traceEnabled) { + this.logger.log(`[ReceiptDecision] ${trace.join(' | ')}`); + } + enriched.push(finalSuggestion + ? { ...item, categorySuggestion: finalSuggestion } + : item); + } + catch (err) { + const traceSignalText = [ + item.rawName, + item.matchedProductName, + item.suggestedProductName, + ] + .filter((v) => typeof v === 'string' && v.trim().length > 0) + .join(' '); + if (this.shouldTraceDecision(traceSignalText || item.rawName)) { + this.logger.warn(`[ReceiptDecision] error raw="${item.rawName}" signal="${traceSignalText || item.rawName}" err=${String(err)}`); + } + enriched.push(item); + } + } + return enriched; + } + shouldTraceDecision(signalText) { + const envFlag = (process.env.RECEIPT_TRACE_DECISIONS ?? '').toLowerCase(); + if (envFlag === '1' || envFlag === 'true' || envFlag === 'yes') { + return true; + } + const normalized = normalizeForRules(signalText); + return hasPorkLikeSignal(normalized); + } + resolvePorkCategory(categories) { + return (categories.find((c) => c.name.toLowerCase() === 'fläsk' && + c.path.toLowerCase().startsWith('kött, chark & fågel > kött > ')) || + categories.find((c) => c.name.toLowerCase() === 'kött' && + c.path.toLowerCase() === 'kött, chark & fågel > kött') || + categories.find((c) => c.path.toLowerCase() === 'kött, chark & fågel')); + } + resolveBreadCategory(categories) { + return (categories.find((c) => c.name.toLowerCase() === 'rostbröd' && + c.path.toLowerCase().startsWith('bröd & kakor > bröd > ')) || + categories.find((c) => c.name.toLowerCase() === 'bröd' && + c.path.toLowerCase() === 'bröd & kakor > bröd') || + categories.find((c) => c.path.toLowerCase() === 'bröd & kakor')); + } + applyHardCategoryOverrides(signalText, suggestion, categories) { + const normalized = normalizeForRules(signalText); + const hasBaconLikeSignal = hasPorkLikeSignal(normalized); + if (!hasBaconLikeSignal) + return suggestion; + const l3Pork = this.resolvePorkCategory(categories); + if (!l3Pork) { + this.logger.warn(`Hard-override: pork signal hittad men ingen köttkategori kunde hittas för "${signalText}"`); + return suggestion; + } + if (suggestion.categoryId === l3Pork.id) + return suggestion; + this.logger.log(`Hard-override: "${signalText}" remappas från "${suggestion.path}" till "${l3Pork.path}"`); + return { + categoryId: l3Pork.id, + categoryName: l3Pork.name, + path: l3Pork.path, + confidence: 'high', + usedFallback: true, + }; + } + ruleBasedCategorySuggestion(rawName, categories) { + const normalized = normalizeForRules(rawName); + const findCategory = (opts) => categories.find((c) => { + const cName = c.name.toLowerCase(); + const cPath = c.path.toLowerCase(); + if (cName !== opts.name.toLowerCase()) + return false; + if (opts.startsWith && !cPath.startsWith(opts.startsWith.toLowerCase())) + return false; + if (opts.includes && !cPath.includes(opts.includes.toLowerCase())) + return false; + return true; + }); + const toSuggestion = (cat, confidence = 'high') => { + if (!cat) + return null; + return { + categoryId: cat.id, + categoryName: cat.name, + path: cat.path, + confidence, + usedFallback: false, + }; + }; + const hasPorkSignal = hasPorkLikeSignal(normalized); + const hasToastBreadSignal = hasBreadLikeSignal(normalized); + if (hasToastBreadSignal) { + const bread = this.resolveBreadCategory(categories); + const hit = toSuggestion(bread, 'high'); + if (hit) + return hit; + } + if (hasPorkSignal) { + const l3Pork = this.resolvePorkCategory(categories); + const hit = toSuggestion(l3Pork, 'high'); + if (hit) + return hit; + } + const hasSausageSignal = /\bkorv\b/.test(normalized) || + /\bfalukorv\b/.test(normalized) || + /\bchorizo\b/.test(normalized) || + /\bbratwurst\b/.test(normalized) || + /\bwienerkorv\b/.test(normalized) || + /\bgrillkorv\b/.test(normalized) || + /\bprinskorv\b/.test(normalized) || + /\bolkorv\b/.test(normalized); + if (hasSausageSignal) { + const isVegetarian = /\bvegetarisk\b/.test(normalized) || + /\bvegansk\b/.test(normalized) || + /\bvego\b/.test(normalized); + if (isVegetarian) { + const vegSausage = findCategory({ + name: 'vegetarisk korv', + startsWith: 'kött, chark & fågel > korv > ', + }); + const hit = toSuggestion(vegSausage, 'high'); + if (hit) + return hit; + } + if (/\bolkorv\b/.test(normalized)) { + const beerSausage = findCategory({ + name: 'ölkorv', + startsWith: 'kött, chark & fågel > korv > ', + }); + const hit = toSuggestion(beerSausage, 'high'); + if (hit) + return hit; + } + const sausageGeneral = findCategory({ + name: 'grill, kok- & kryddkorv', + startsWith: 'kött, chark & fågel > korv > ', + }); + const hit = toSuggestion(sausageGeneral, 'high'); + if (hit) + return hit; + } + const hasPoultrySignal = /\bkyckling\b/.test(normalized) || + /\bkalkon\b/.test(normalized) || + /\bdrumsticks?\b/.test(normalized) || + /\bling?file\b/.test(normalized) || + /\blarfile\b/.test(normalized) || + /\bkycklinglar\b/.test(normalized); + if (hasPoultrySignal) { + const isFrozen = /\bfryst\b/.test(normalized) || /\bdjupfryst\b/.test(normalized); + if (isFrozen) { + const frozenPoultry = findCategory({ + name: 'fryst fågel', + startsWith: 'kött, chark & fågel > fågel > ', + }); + const hit = toSuggestion(frozenPoultry, 'high'); + if (hit) + return hit; + } + const freshPoultry = findCategory({ + name: 'färsk fågel', + startsWith: 'kött, chark & fågel > fågel > ', + }); + const hit = toSuggestion(freshPoultry, 'high'); + if (hit) + return hit; + } + const hasColdCutSignal = /\bpalagg\b/.test(normalized) || + /\bpalegg\b/.test(normalized) || + /\bskivad\b/.test(normalized) || + /\bsalami\b/.test(normalized) || + /\bmedvurst\b/.test(normalized) || + /\bpastrami\b/.test(normalized) || + /\bskinka\b/.test(normalized); + if (hasColdCutSignal) { + const hasSlicedSausageSignal = /\bsalami\b/.test(normalized) || + /\bmedvurst\b/.test(normalized) || + /\bpastrami\b/.test(normalized); + if (hasSlicedSausageSignal) { + const salamiColdCut = findCategory({ + name: 'korv & salami', + startsWith: 'kött, chark & fågel > pålägg > ', + }); + const hit = toSuggestion(salamiColdCut, 'high'); + if (hit) + return hit; + } + const slicedColdCut = findCategory({ + name: 'skivat pålägg', + startsWith: 'kött, chark & fågel > pålägg > ', + }); + const hit = toSuggestion(slicedColdCut, 'high'); + if (hit) + return hit; + } + const hasMinceSignal = /\bfars\b/.test(normalized) || + /\bfarse\b/.test(normalized) || + /\bmince\b/.test(normalized) || + /\bköttfärs\b/.test(rawName.toLowerCase()); + if (hasMinceSignal && !hasPoultrySignal) { + const mince = findCategory({ + name: 'köttfärs', + startsWith: 'kött, chark & fågel > kött > ', + }); + const hit = toSuggestion(mince, 'high'); + if (hit) + return hit; + } + const hasBeefVealSignal = /\bnot\b/.test(normalized) || + /\bkalv\b/.test(normalized) || + /\bbiff\b/.test(normalized) || + /\bentrecote\b/.test(normalized) || + /\brostbiff\b/.test(normalized) || + /\bryggbiff\b/.test(normalized); + if (hasBeefVealSignal && !hasMinceSignal) { + const beefVeal = findCategory({ + name: 'nöt & kalv', + startsWith: 'kött, chark & fågel > kött > ', + }); + const hit = toSuggestion(beefVeal, 'high'); + if (hit) + return hit; + } + const hasPastaSignal = /\bmezze\b/.test(normalized) || + /\bmaniche\b/.test(normalized) || + /\bpenne\b/.test(normalized) || + /\brigatoni\b/.test(normalized) || + /\bfusilli\b/.test(normalized) || + /\bspaghetti\b/.test(normalized) || + /\btagliatelle\b/.test(normalized) || + /\bmakaron\w*\b/.test(normalized) || + /\bgnocchi\b/.test(normalized) || + /\blasagne\b/.test(normalized) || + /\bpasta\b/.test(normalized); + if (hasPastaSignal) { + const freshPasta = findCategory({ + name: 'färsk pasta', + startsWith: 'skafferi > pasta, ris & matgryn > ', + }); + if (/\bfarsk\b/.test(normalized) || /\bfresh\b/.test(normalized)) { + const freshHit = toSuggestion(freshPasta, 'high'); + if (freshHit) + return freshHit; + } + const pasta = findCategory({ + name: 'pasta', + startsWith: 'skafferi > pasta, ris & matgryn > ', + }); + const pastaHit = toSuggestion(pasta, 'high'); + if (pastaHit) + return pastaHit; + } + const hasCreamSignal = /\bvispgradde\b/.test(normalized) || + /\bmatlagningsgradde\b/.test(normalized) || + /\bgradde\b/.test(normalized) || + /\bcreme\s+fraiche\b/.test(normalized) || + /\bgraddfil\b/.test(normalized); + const hasPlantOrAllergySignal = /\blaktosfri\b/.test(normalized) || + /\bvegetabilisk\b/.test(normalized) || + /\bhavre\b/.test(normalized) || + /\bsoja\b/.test(normalized) || + /\brisdryck\b/.test(normalized) || + /\bplant\b/.test(normalized); + if (hasCreamSignal && !hasPlantOrAllergySignal) { + const l3Cream = findCategory({ + name: 'grädde', + startsWith: 'mejeri, ost & ägg > matlagning > ', + }); + const l3Hit = toSuggestion(l3Cream, 'high'); + if (l3Hit) + return l3Hit; + const l2CookingDairy = findCategory({ + name: 'matlagning', + startsWith: 'mejeri, ost & ägg > ', + }); + const hit = toSuggestion(l2CookingDairy, 'high'); + if (hit) + return hit; + } + const hasMilkSignal = /\bmjolk\b/.test(normalized) || + /\bstandardmjolk\b/.test(normalized) || + /\bstandmjolk\b/.test(normalized) || + /\besl\b/.test(normalized); + const hasLactoseFreeSignal = /\blaktosfri\b/.test(normalized) || + /\blactose\s*free\b/.test(normalized); + if (hasMilkSignal && !hasPlantOrAllergySignal && !hasLactoseFreeSignal) { + const l3StandardMilk = findCategory({ + name: 'standardmjölk', + startsWith: 'mejeri, ost & ägg > mjölk > ', + }); + const hit = toSuggestion(l3StandardMilk, 'high'); + if (hit) + return hit; + const l2Milk = findCategory({ + name: 'mjölk', + startsWith: 'mejeri, ost & ägg > ', + }); + const fallbackHit = toSuggestion(l2Milk, 'high'); + if (fallbackHit) + return fallbackHit; + } + const hasEggSignal = /\bagg\b/.test(normalized) || + /\begg\b/.test(normalized) || + /\binne\b/.test(normalized) || + /\b24p\b/.test(normalized); + if (hasEggSignal) { + const l2Egg = categories.find((c) => c.name.toLowerCase() === 'ägg' && + c.path.toLowerCase() === 'mejeri, ost & ägg > ägg'); + if (l2Egg) { + return { + categoryId: l2Egg.id, + categoryName: l2Egg.name, + path: l2Egg.path, + confidence: 'high', + usedFallback: false, + }; + } + const l1DairyEgg = categories.find((c) => c.path.toLowerCase() === 'mejeri, ost & ägg'); + if (l1DairyEgg) { + return { + categoryId: l1DairyEgg.id, + categoryName: l1DairyEgg.name, + path: l1DairyEgg.path, + confidence: 'high', + usedFallback: false, + }; + } + } + const hasJuiceSignal = /\bjuice\b/.test(normalized) || + /\bnektar\b/.test(normalized) || + /\bfruktdryck\b/.test(normalized) || + /\bsmoothie\b/.test(normalized) || + /\bmultivitamin\b/.test(normalized); + if (hasJuiceSignal) { + const l3ColdJuice = findCategory({ + name: 'kyld juice & nektar', + startsWith: 'dryck > juice, fruktdryck & smoothie > ', + }); + const l3Hit = toSuggestion(l3ColdJuice, 'high'); + if (l3Hit) + return l3Hit; + const l2Juice = findCategory({ + name: 'juice, fruktdryck & smoothie', + startsWith: 'dryck > ', + }); + const l2Hit = toSuggestion(l2Juice, 'high'); + if (l2Hit) + return l2Hit; + } + const isTea = /\bte\b/.test(normalized) || + /\btea\b/.test(normalized) || + /\bchai\b/.test(normalized) || + /\btepa(se|k|r)?\b/.test(normalized); + if (isTea) { + const l3Te = categories.find((c) => c.name.toLowerCase() === 'te' && c.path.toLowerCase().includes('te & choklad')); + if (l3Te) { + return { categoryId: l3Te.id, categoryName: l3Te.name, path: l3Te.path, confidence: 'high', usedFallback: false }; + } + const l2TeChoklad = categories.find((c) => c.name.toLowerCase() === 'te & choklad' && c.path.toLowerCase().startsWith('dryck')); + if (l2TeChoklad) { + return { categoryId: l2TeChoklad.id, categoryName: l2TeChoklad.name, path: l2TeChoklad.path, confidence: 'medium', usedFallback: false }; + } + } + const isKaffebrod = /\bkaffebrod\b/.test(normalized) || + /\bwienerbrod\b/.test(normalized) || + /\bdonut\b/.test(normalized) || + /\bmunk\b/.test(normalized) || + /\bcroissant\b/.test(normalized) || + /\bkanelbulle\b/.test(normalized) || + /\bbakelse\b/.test(normalized) || + /\bsemla\b/.test(normalized) || + /\bdammsugare\b/.test(normalized) || + /\bkladdkaka\b/.test(normalized) || + /\bmuffin\b/.test(normalized) || + /\bcupcake\b/.test(normalized) || + /\bchokladboll\b/.test(normalized); + if (isKaffebrod) { + const l3Kaffebrod = categories.find((c) => c.name.toLowerCase() === 'kaffebröd' && c.path.toLowerCase().includes('kondis & fika')); + if (l3Kaffebrod) { + return { categoryId: l3Kaffebrod.id, categoryName: l3Kaffebrod.name, path: l3Kaffebrod.path, confidence: 'high', usedFallback: false }; + } + const l2Kondis = categories.find((c) => c.name.toLowerCase() === 'kondis & fika' && c.path.toLowerCase().startsWith('bröd & kakor')); + if (l2Kondis) { + return { categoryId: l2Kondis.id, categoryName: l2Kondis.name, path: l2Kondis.path, confidence: 'medium', usedFallback: false }; + } + } + const isChocolateBar = /\bsnickers\b/.test(normalized) || + /\bmars\b/.test(normalized) || + /\btwix\b/.test(normalized) || + /\bbounty\b/.test(normalized) || + /\bkitkat\b/.test(normalized) || + /\bdajm\b/.test(normalized) || + /\bjapp\b/.test(normalized); + if (isChocolateBar) { + const l3ChocolateBars = findCategory({ + name: 'chokladkakor & rullar', + startsWith: 'glass, godis & snacks > choklad > ', + }); + const hit = toSuggestion(l3ChocolateBars, 'high'); + if (hit) + return hit; + } + const isCandyBagLike = /\bnappar\b/.test(normalized) || + /\bgodispas\w*\b/.test(normalized); + if (isCandyBagLike) { + const l3CandyBag = findCategory({ + name: 'godispåsar', + startsWith: 'glass, godis & snacks > godis > ', + }); + const hit = toSuggestion(l3CandyBag, 'high'); + if (hit) + return hit; + } + const hasPotatoSignal = /\bpotatis\b/.test(normalized); + const hasFrozenPotatoSignal = /\bfryst\b/.test(normalized) || + /\bdjupfryst\b/.test(normalized) || + /\bpommes\b/.test(normalized) || + /\bstrips?\b/.test(normalized); + if (hasPotatoSignal && !hasFrozenPotatoSignal) { + const l3Potato = findCategory({ + name: 'potatis', + startsWith: 'frukt & grönt > potatis & rotsaker > ', + }); + const l3Hit = toSuggestion(l3Potato, 'high'); + if (l3Hit) + return l3Hit; + } + const isCookingBase = /\bmatlagningsbas\b/.test(normalized) || + /\bmatlagnings\b/.test(normalized) || + /\bplant\s+cream\b/.test(normalized) || + /\bcreme\s+fraiche\b/.test(normalized) || + /\bgradde\b/.test(normalized) || + /\bvispgradde\b/.test(normalized); + const isPlantOrAllergy = /\blaktosfri\b/.test(normalized) || + /\bvegetabilisk\b/.test(normalized) || + /\bhavre\b/.test(normalized) || + /\bsoja\b/.test(normalized) || + /\brisdryck\b/.test(normalized) || + /\bplant\b/.test(normalized); + if (!isCookingBase || !isPlantOrAllergy) + return null; + const l3AllergyCooking = categories.find((c) => c.name.toLowerCase() === 'allergi matlagning' && + c.path.toLowerCase().startsWith('matlagning > ')); + if (l3AllergyCooking) { + return { + categoryId: l3AllergyCooking.id, + categoryName: l3AllergyCooking.name, + path: l3AllergyCooking.path, + confidence: 'high', + usedFallback: false, + }; + } + const l2Cooking = categories.find((c) => c.name.toLowerCase() === 'matlagning' && + c.path.toLowerCase() === 'mejeri, ost & ägg > matlagning'); + if (l2Cooking) { + return { + categoryId: l2Cooking.id, + categoryName: l2Cooking.name, + path: l2Cooking.path, + confidence: 'medium', + usedFallback: false, + }; + } + return null; + } + applyContradictionGuard(rawName, suggestion, categories) { + const normalized = normalizeForRules(rawName); + const hasPorkSignal = hasPorkLikeSignal(normalized); + if (hasPorkSignal) { + const aiPath = suggestion.path.toLowerCase(); + const isClearlyWrongBranch = aiPath.includes('köttbullar & färsprodukter') || aiPath.includes('köttfärs'); + if (!isClearlyWrongBranch) + return suggestion; + const l3Pork = this.resolvePorkCategory(categories); + if (!l3Pork) + return suggestion; + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l3Pork.path}"`); + return { + categoryId: l3Pork.id, + categoryName: l3Pork.name, + path: l3Pork.path, + confidence: 'high', + usedFallback: true, + }; + } + const hasMilkSignal = /\bmjolk\b/.test(normalized) || + /\bstandardmjolk\b/.test(normalized) || + /\bstandmjolk\b/.test(normalized) || + /\besl\b/.test(normalized); + const hasLactoseFreeSignal = /\blaktosfri\b/.test(normalized) || + /\blactose\s*free\b/.test(normalized); + if (hasMilkSignal && !hasLactoseFreeSignal) { + const isWrongLactoseFreeBranch = suggestion.path.toLowerCase().includes('allergi mejeri > laktosfri mjölk'); + if (isWrongLactoseFreeBranch) { + const l3StandardMilk = categories.find((c) => c.name.toLowerCase() === 'standardmjölk' && + c.path.toLowerCase().startsWith('mejeri, ost & ägg > mjölk > ')); + if (l3StandardMilk) { + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l3StandardMilk.path}"`); + return { + categoryId: l3StandardMilk.id, + categoryName: l3StandardMilk.name, + path: l3StandardMilk.path, + confidence: 'high', + usedFallback: true, + }; + } + } + } + const hasEggSignal = /\bagg\b/.test(normalized) || + /\begg\b/.test(normalized) || + /\binne\b/.test(normalized) || + /\b24p\b/.test(normalized); + if (hasEggSignal && suggestion.path.toLowerCase().includes('allergi mejeri')) { + const l2Egg = categories.find((c) => c.name.toLowerCase() === 'ägg' && + c.path.toLowerCase() === 'mejeri, ost & ägg > ägg'); + if (l2Egg) { + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l2Egg.path}"`); + return { + categoryId: l2Egg.id, + categoryName: l2Egg.name, + path: l2Egg.path, + confidence: 'high', + usedFallback: true, + }; + } + const l1DairyEgg = categories.find((c) => c.path.toLowerCase() === 'mejeri, ost & ägg'); + if (l1DairyEgg) { + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l1DairyEgg.path}"`); + return { + categoryId: l1DairyEgg.id, + categoryName: l1DairyEgg.name, + path: l1DairyEgg.path, + confidence: 'high', + usedFallback: true, + }; + } + } + const hasCreamSignal = /\bvispgradde\b/.test(normalized) || + /\bmatlagningsgradde\b/.test(normalized) || + /\bgradde\b/.test(normalized) || + /\bcreme\s+fraiche\b/.test(normalized) || + /\bgraddfil\b/.test(normalized); + const hasPlantOrAllergySignal = /\blaktosfri\b/.test(normalized) || + /\bvegetabilisk\b/.test(normalized) || + /\bhavre\b/.test(normalized) || + /\bsoja\b/.test(normalized) || + /\brisdryck\b/.test(normalized) || + /\bplant\b/.test(normalized); + if (hasCreamSignal && !hasPlantOrAllergySignal) { + const aiPath = suggestion.path.toLowerCase(); + const isOutsideDairy = !aiPath.startsWith('mejeri, ost & ägg > matlagning'); + if (!isOutsideDairy) + return suggestion; + const l2CookingDairy = categories.find((c) => c.name.toLowerCase() === 'matlagning' && + c.path.toLowerCase() === 'mejeri, ost & ägg > matlagning'); + if (!l2CookingDairy) + return suggestion; + const l3Cream = categories.find((c) => c.name.toLowerCase() === 'grädde' && + c.path.toLowerCase().startsWith('mejeri, ost & ägg > matlagning > ')); + if (l3Cream) { + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l3Cream.path}"`); + return { + categoryId: l3Cream.id, + categoryName: l3Cream.name, + path: l3Cream.path, + confidence: 'high', + usedFallback: true, + }; + } + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${l2CookingDairy.path}"`); + return { + categoryId: l2CookingDairy.id, + categoryName: l2CookingDairy.name, + path: l2CookingDairy.path, + confidence: 'high', + usedFallback: true, + }; + } + const hasToastBreadSignal = hasBreadLikeSignal(normalized); + if (hasToastBreadSignal) { + const aiPath = suggestion.path.toLowerCase(); + const isOutsideBread = !aiPath.startsWith('bröd & kakor > bröd'); + if (!isOutsideBread) + return suggestion; + const bread = this.resolveBreadCategory(categories); + if (!bread) + return suggestion; + this.logger.log(`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${bread.path}"`); + return { + categoryId: bread.id, + categoryName: bread.name, + path: bread.path, + confidence: 'high', + usedFallback: true, + }; + } + return suggestion; + } +}; +exports.ReceiptImportService = ReceiptImportService; +exports.ReceiptImportService = ReceiptImportService = ReceiptImportService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + ai_service_1.AiService, + categories_service_1.CategoriesService]) +], ReceiptImportService); +//# sourceMappingURL=receipt-import.service.js.map \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.service.js.map b/backend/dist/receipt-import/receipt-import.service.js.map new file mode 100644 index 00000000..a57939f9 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-import.service.js","sourceRoot":"","sources":["../../src/receipt-import/receipt-import.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.service.spec.d.ts b/backend/dist/receipt-import/receipt-import.service.spec.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/backend/dist/receipt-import/receipt-import.service.spec.js b/backend/dist/receipt-import/receipt-import.service.spec.js new file mode 100644 index 00000000..01681144 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.spec.js @@ -0,0 +1,176 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const receipt_import_service_1 = require("./receipt-import.service"); +function cat(id, name, path) { + return { id, name, path }; +} +describe('ReceiptImportService test matrix', () => { + const categories = [ + cat(1, 'Bröd & Kakor', 'Bröd & Kakor'), + cat(2, 'Kondis & fika', 'Bröd & Kakor > Kondis & fika'), + cat(3, 'Kaffebröd', 'Bröd & Kakor > Kondis & fika > Kaffebröd'), + cat(10, 'Skafferi', 'Skafferi'), + cat(11, 'Pasta, ris & matgryn', 'Skafferi > Pasta, ris & matgryn'), + cat(12, 'Pasta', 'Skafferi > Pasta, ris & matgryn > Pasta'), + cat(20, 'Frukt & Grönt', 'Frukt & Grönt'), + cat(21, 'Potatis & rotsaker', 'Frukt & Grönt > Potatis & rotsaker'), + cat(22, 'Potatis', 'Frukt & Grönt > Potatis & rotsaker > Potatis'), + cat(30, 'Mejeri, ost & ägg', 'Mejeri, ost & ägg'), + cat(31, 'Matlagning', 'Mejeri, ost & ägg > Matlagning'), + cat(32, 'Grädde', 'Mejeri, ost & ägg > Matlagning > Grädde'), + cat(33, 'Ägg', 'Mejeri, ost & ägg > Ägg'), + cat(40, 'Dryck', 'Dryck'), + cat(41, 'Juice, fruktdryck & smoothie', 'Dryck > Juice, fruktdryck & smoothie'), + cat(42, 'Kyld juice & nektar', 'Dryck > Juice, fruktdryck & smoothie > Kyld juice & nektar'), + cat(50, 'Glass, godis & snacks', 'Glass, godis & snacks'), + cat(51, 'Godis', 'Glass, godis & snacks > Godis'), + cat(52, 'Godispåsar', 'Glass, godis & snacks > Godis > Godispåsar'), + cat(53, 'Choklad', 'Glass, godis & snacks > Choklad'), + cat(54, 'Chokladkakor & rullar', 'Glass, godis & snacks > Choklad > Chokladkakor & rullar'), + ]; + const prismaMock = { + category: { findMany: jest.fn().mockResolvedValue([]) }, + receiptAlias: { findMany: jest.fn().mockResolvedValue([]) }, + product: { findMany: jest.fn().mockResolvedValue([]) }, + }; + const aiServiceMock = { + suggestCategory: jest.fn(), + }; + const categoriesServiceMock = { + findFlattened: jest.fn(), + }; + const service = new receipt_import_service_1.ReceiptImportService(prismaMock, aiServiceMock, categoriesServiceMock); + beforeEach(() => { + jest.clearAllMocks(); + prismaMock.receiptAlias.findMany.mockResolvedValue([]); + prismaMock.product.findMany.mockResolvedValue([]); + }); + describe('ignore patterns', () => { + it.each([ + 'Willys Plus:Bröd', + 'willys plus: mjölk', + 'WILLYS PLUS - ÄGG', + 'Willys Plus : Ost', + 'Rabatt kupong', + 'Summa', + ])('ignorerar "%s"', (raw) => { + expect((0, receipt_import_service_1.isIgnoredReceiptName)(raw)).toBe(true); + }); + it.each([ + 'Mezze Maniche', + 'Snickers', + 'Nappar Cola 80g', + 'Vispgrädde 5DL', + ])('ignorerar inte "%s"', (raw) => { + expect((0, receipt_import_service_1.isIgnoredReceiptName)(raw)).toBe(false); + }); + }); + describe('rule matrix', () => { + const matrix = [ + { raw: 'Mezze Maniche', expectedPath: 'Skafferi > Pasta, ris & matgryn > Pasta' }, + { raw: 'Nappar Cola 80g', expectedPath: 'Glass, godis & snacks > Godis > Godispåsar' }, + { raw: 'Snickers', expectedPath: 'Glass, godis & snacks > Choklad > Chokladkakor & rullar' }, + { raw: 'Potatis Fast', expectedPath: 'Frukt & Grönt > Potatis & rotsaker > Potatis' }, + { raw: 'Ägg 24p Inne M', expectedPath: 'Mejeri, ost & ägg > Ägg' }, + { raw: 'Dryck Multivitamin', expectedPath: 'Dryck > Juice, fruktdryck & smoothie > Kyld juice & nektar' }, + { raw: 'Vispgrädde 5DL', expectedPath: 'Mejeri, ost & ägg > Matlagning > Grädde' }, + { raw: 'Wienerbröd', expectedPath: 'Bröd & Kakor > Kondis & fika > Kaffebröd' }, + ]; + it.each(matrix)('klassar "$raw" -> "$expectedPath"', ({ raw, expectedPath }) => { + const suggestion = service.ruleBasedCategorySuggestion(raw, categories); + expect(suggestion).not.toBeNull(); + expect(suggestion?.path).toBe(expectedPath); + }); + }); + describe('alias fallback och prioritet', () => { + it('prioriterar user-alias före global alias för samma receiptName', async () => { + prismaMock.receiptAlias.findMany.mockResolvedValue([ + { + receiptName: 'mjolk 1l', + productId: 501, + product: { + id: 501, + name: 'Mjolk user', + canonicalName: 'Mjolk user', + categoryId: 30, + categoryRef: { id: 30, name: 'Mejeri' }, + }, + }, + { + receiptName: 'mjolk 1l', + productId: 999, + product: { + id: 999, + name: 'Mjolk global', + canonicalName: 'Mjolk global', + categoryId: 30, + categoryRef: { id: 30, name: 'Mejeri' }, + }, + }, + ]); + prismaMock.product.findMany.mockResolvedValue([]); + const result = await service.matchProducts([{ rawName: 'MJOLK 1L' }], 77); + expect(prismaMock.receiptAlias.findMany).toHaveBeenCalledWith(expect.objectContaining({ + where: { + OR: [ + { ownerId: 77, isGlobal: false }, + { isGlobal: true }, + ], + }, + })); + expect(result[0].matchedProductId).toBe(501); + expect(result[0].matchedProductName).toBe('Mjolk user'); + }); + it('använder global alias när user-alias saknas', async () => { + prismaMock.receiptAlias.findMany.mockResolvedValue([ + { + receiptName: 'snickers', + productId: 222, + product: { + id: 222, + name: 'Snickers', + canonicalName: 'Snickers', + categoryId: 53, + categoryRef: { id: 53, name: 'Choklad' }, + }, + }, + ]); + prismaMock.product.findMany.mockResolvedValue([]); + const result = await service.matchProducts([{ rawName: 'SNICKERS' }], 88); + expect(result[0].matchedProductId).toBe(222); + expect(result[0].matchedProductName).toBe('Snickers'); + }); + it('flöde: manuell korrigering lär alias och nästa import matchar direkt', async () => { + const aliases = []; + prismaMock.receiptAlias.findMany.mockImplementation(async () => aliases); + prismaMock.product.findMany.mockResolvedValue([ + { + id: 700, + name: 'Arla Mjolk 1l', + canonicalName: 'Mjolk', + categoryId: 30, + categoryRef: { id: 30, name: 'Mejeri' }, + }, + ]); + const first = await service.matchProducts([{ rawName: 'ARLA MJOLK 1L' }], 42); + expect(first[0].matchedProductId).toBeUndefined(); + expect(first[0].suggestedProductId).toBe(700); + aliases.push({ + receiptName: 'arla mjolk 1l', + productId: 700, + product: { + id: 700, + name: 'Arla Mjolk 1l', + canonicalName: 'Mjolk', + categoryId: 30, + categoryRef: { id: 30, name: 'Mejeri' }, + }, + }); + const second = await service.matchProducts([{ rawName: 'ARLA MJOLK 1L' }], 42); + expect(second[0].matchedProductId).toBe(700); + expect(second[0].matchedProductName).toBe('Mjolk'); + expect(second[0].suggestedProductId).toBeUndefined(); + }); + }); +}); +//# sourceMappingURL=receipt-import.service.spec.js.map \ No newline at end of file diff --git a/backend/dist/receipt-import/receipt-import.service.spec.js.map b/backend/dist/receipt-import/receipt-import.service.spec.js.map new file mode 100644 index 00000000..5d2867d2 --- /dev/null +++ b/backend/dist/receipt-import/receipt-import.service.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"receipt-import.service.spec.js","sourceRoot":"","sources":["../../src/receipt-import/receipt-import.service.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-ingredient.dto.d.ts b/backend/dist/recipes/dto/create-ingredient.dto.d.ts new file mode 100644 index 00000000..8e6257fe --- /dev/null +++ b/backend/dist/recipes/dto/create-ingredient.dto.d.ts @@ -0,0 +1,6 @@ +export declare class CreateIngredientDto { + productId: number; + quantity: number; + unit: string; + note?: string; +} diff --git a/backend/dist/recipes/dto/create-ingredient.dto.js b/backend/dist/recipes/dto/create-ingredient.dto.js new file mode 100644 index 00000000..8663a779 --- /dev/null +++ b/backend/dist/recipes/dto/create-ingredient.dto.js @@ -0,0 +1,34 @@ +"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.CreateIngredientDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateIngredientDto { +} +exports.CreateIngredientDto = CreateIngredientDto; +__decorate([ + (0, class_validator_1.IsNumber)(), + __metadata("design:type", Number) +], CreateIngredientDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsNumber)(), + __metadata("design:type", Number) +], CreateIngredientDto.prototype, "quantity", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateIngredientDto.prototype, "unit", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateIngredientDto.prototype, "note", void 0); +//# sourceMappingURL=create-ingredient.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-ingredient.dto.js.map b/backend/dist/recipes/dto/create-ingredient.dto.js.map new file mode 100644 index 00000000..6c292849 --- /dev/null +++ b/backend/dist/recipes/dto/create-ingredient.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-ingredient.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/create-ingredient.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAiE;AAEjE,MAAa,mBAAmB;CAa/B;AAbD,kDAaC;AAXC;IADC,IAAA,0BAAQ,GAAE;;sDACO;AAGlB;IADC,IAAA,0BAAQ,GAAE;;qDACM;AAGjB;IADC,IAAA,0BAAQ,GAAE;;iDACE;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;iDACG"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-recipe-ingredient.dto.d.ts b/backend/dist/recipes/dto/create-recipe-ingredient.dto.d.ts new file mode 100644 index 00000000..ee4902f2 --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe-ingredient.dto.d.ts @@ -0,0 +1,6 @@ +export declare class CreateRecipeIngredientDto { + productId: number; + quantity: number; + unit: string; + note?: string; +} diff --git a/backend/dist/recipes/dto/create-recipe-ingredient.dto.js b/backend/dist/recipes/dto/create-recipe-ingredient.dto.js new file mode 100644 index 00000000..ee863c49 --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe-ingredient.dto.js @@ -0,0 +1,35 @@ +"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.CreateRecipeIngredientDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateRecipeIngredientDto { +} +exports.CreateRecipeIngredientDto = CreateRecipeIngredientDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateRecipeIngredientDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], CreateRecipeIngredientDto.prototype, "quantity", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "unit", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "note", void 0); +//# sourceMappingURL=create-recipe-ingredient.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-recipe-ingredient.dto.js.map b/backend/dist/recipes/dto/create-recipe-ingredient.dto.js.map new file mode 100644 index 00000000..2f72399d --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe-ingredient.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-recipe-ingredient.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/create-recipe-ingredient.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA6E;AAE7E,MAAa,yBAAyB;CAcrC;AAdD,8DAcC;AAZC;IADC,IAAA,uBAAK,GAAE;;4DACW;AAInB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;2DACW;AAGlB;IADC,IAAA,0BAAQ,GAAE;;uDACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACG"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-recipe.dto.d.ts b/backend/dist/recipes/dto/create-recipe.dto.d.ts new file mode 100644 index 00000000..c61c2774 --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe.dto.d.ts @@ -0,0 +1,21 @@ +declare class CreateRecipeIngredientDto { + productId?: number; + rawName: string; + rawLine?: string; + quantity?: number; + unit?: string; + note?: string; + matchConfidence?: number; + matchSource?: string; + alternativeProductIds?: number[]; +} +export declare class CreateRecipeDto { + name: string; + description?: string; + instructions?: string; + imageUrl?: string; + servings?: number; + isPublic?: boolean; + ingredients: CreateRecipeIngredientDto[]; +} +export {}; diff --git a/backend/dist/recipes/dto/create-recipe.dto.js b/backend/dist/recipes/dto/create-recipe.dto.js new file mode 100644 index 00000000..e6bd3b48 --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe.dto.js @@ -0,0 +1,104 @@ +"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.CreateRecipeDto = void 0; +const class_validator_1 = require("class-validator"); +const class_transformer_1 = require("class-transformer"); +class CreateRecipeIngredientDto { +} +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateRecipeIngredientDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "rawName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "rawLine", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], CreateRecipeIngredientDto.prototype, "quantity", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "unit", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "note", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsNumber)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], CreateRecipeIngredientDto.prototype, "matchConfidence", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeIngredientDto.prototype, "matchSource", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], CreateRecipeIngredientDto.prototype, "alternativeProductIds", void 0); +class CreateRecipeDto { +} +exports.CreateRecipeDto = CreateRecipeDto; +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeDto.prototype, "instructions", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateRecipeDto.prototype, "imageUrl", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], CreateRecipeDto.prototype, "servings", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], CreateRecipeDto.prototype, "isPublic", void 0); +__decorate([ + (0, class_validator_1.IsArray)(), + (0, class_validator_1.ArrayMinSize)(1), + (0, class_validator_1.ValidateNested)({ each: true }), + (0, class_transformer_1.Type)(() => CreateRecipeIngredientDto), + __metadata("design:type", Array) +], CreateRecipeDto.prototype, "ingredients", void 0); +//# sourceMappingURL=create-recipe.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/create-recipe.dto.js.map b/backend/dist/recipes/dto/create-recipe.dto.js.map new file mode 100644 index 00000000..26fb5337 --- /dev/null +++ b/backend/dist/recipes/dto/create-recipe.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-recipe.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/create-recipe.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAUyB;AACzB,yDAAyC;AAEzC,MAAM,yBAAyB;CAsC9B;AAnCC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;4DACW;AAGnB;IADC,IAAA,0BAAQ,GAAE;;0DACM;AAIjB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;0DACM;AAKjB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;2DACW;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACG;AAKd;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;kEACkB;AAIzB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;8DACU;AAKrB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,uBAAK,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;wEACW;AAGnC,MAAa,eAAe;CA8B3B;AA9BD,0CA8BC;AA5BC;IADC,IAAA,0BAAQ,GAAE;;6CACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACU;AAIrB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;qDACW;AAItB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;iDACO;AAKlB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;iDACW;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;iDACO;AAMnB;IAJC,IAAA,yBAAO,GAAE;IACT,IAAA,8BAAY,EAAC,CAAC,CAAC;IACf,IAAA,gCAAc,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,yBAAyB,CAAC;;oDACI"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/parse-markdown.dto.d.ts b/backend/dist/recipes/dto/parse-markdown.dto.d.ts new file mode 100644 index 00000000..7a779be8 --- /dev/null +++ b/backend/dist/recipes/dto/parse-markdown.dto.d.ts @@ -0,0 +1,3 @@ +export declare class ParseMarkdownDto { + markdown: string; +} diff --git a/backend/dist/recipes/dto/parse-markdown.dto.js b/backend/dist/recipes/dto/parse-markdown.dto.js new file mode 100644 index 00000000..72d70c29 --- /dev/null +++ b/backend/dist/recipes/dto/parse-markdown.dto.js @@ -0,0 +1,22 @@ +"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.ParseMarkdownDto = void 0; +const class_validator_1 = require("class-validator"); +class ParseMarkdownDto { +} +exports.ParseMarkdownDto = ParseMarkdownDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(1), + __metadata("design:type", String) +], ParseMarkdownDto.prototype, "markdown", void 0); +//# sourceMappingURL=parse-markdown.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/parse-markdown.dto.js.map b/backend/dist/recipes/dto/parse-markdown.dto.js.map new file mode 100644 index 00000000..fc9e717a --- /dev/null +++ b/backend/dist/recipes/dto/parse-markdown.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parse-markdown.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/parse-markdown.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAsD;AAEtD,MAAa,gBAAgB;CAI5B;AAJD,4CAIC;AADC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;kDACK"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/set-recipe-visibility.dto.d.ts b/backend/dist/recipes/dto/set-recipe-visibility.dto.d.ts new file mode 100644 index 00000000..3d8e0c5d --- /dev/null +++ b/backend/dist/recipes/dto/set-recipe-visibility.dto.d.ts @@ -0,0 +1,3 @@ +export declare class SetRecipeVisibilityDto { + isPublic: boolean; +} diff --git a/backend/dist/recipes/dto/set-recipe-visibility.dto.js b/backend/dist/recipes/dto/set-recipe-visibility.dto.js new file mode 100644 index 00000000..2c4ad921 --- /dev/null +++ b/backend/dist/recipes/dto/set-recipe-visibility.dto.js @@ -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.SetRecipeVisibilityDto = void 0; +const class_validator_1 = require("class-validator"); +class SetRecipeVisibilityDto { +} +exports.SetRecipeVisibilityDto = SetRecipeVisibilityDto; +__decorate([ + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], SetRecipeVisibilityDto.prototype, "isPublic", void 0); +//# sourceMappingURL=set-recipe-visibility.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/set-recipe-visibility.dto.js.map b/backend/dist/recipes/dto/set-recipe-visibility.dto.js.map new file mode 100644 index 00000000..a8c2e047 --- /dev/null +++ b/backend/dist/recipes/dto/set-recipe-visibility.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"set-recipe-visibility.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/set-recipe-visibility.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA4C;AAE5C,MAAa,sBAAsB;CAGlC;AAHD,wDAGC;AADC;IADC,IAAA,2BAAS,GAAE;;wDACO"} \ No newline at end of file diff --git a/backend/dist/recipes/dto/share-recipe.dto.d.ts b/backend/dist/recipes/dto/share-recipe.dto.d.ts new file mode 100644 index 00000000..6e8fe679 --- /dev/null +++ b/backend/dist/recipes/dto/share-recipe.dto.d.ts @@ -0,0 +1,3 @@ +export declare class ShareRecipeDto { + username: string; +} diff --git a/backend/dist/recipes/dto/share-recipe.dto.js b/backend/dist/recipes/dto/share-recipe.dto.js new file mode 100644 index 00000000..8b1ffb70 --- /dev/null +++ b/backend/dist/recipes/dto/share-recipe.dto.js @@ -0,0 +1,22 @@ +"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.ShareRecipeDto = void 0; +const class_validator_1 = require("class-validator"); +class ShareRecipeDto { +} +exports.ShareRecipeDto = ShareRecipeDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(2), + __metadata("design:type", String) +], ShareRecipeDto.prototype, "username", void 0); +//# sourceMappingURL=share-recipe.dto.js.map \ No newline at end of file diff --git a/backend/dist/recipes/dto/share-recipe.dto.js.map b/backend/dist/recipes/dto/share-recipe.dto.js.map new file mode 100644 index 00000000..6e39b640 --- /dev/null +++ b/backend/dist/recipes/dto/share-recipe.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"share-recipe.dto.js","sourceRoot":"","sources":["../../../src/recipes/dto/share-recipe.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAsD;AAEtD,MAAa,cAAc;CAI1B;AAJD,wCAIC;AADC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;gDACK"} \ No newline at end of file diff --git a/backend/dist/recipes/recipes.controller.d.ts b/backend/dist/recipes/recipes.controller.d.ts new file mode 100644 index 00000000..c77eb3f2 --- /dev/null +++ b/backend/dist/recipes/recipes.controller.d.ts @@ -0,0 +1,646 @@ +import { RecipesService } from './recipes.service'; +import { CreateRecipeDto } from './dto/create-recipe.dto'; +import { CreateIngredientDto } from './dto/create-ingredient.dto'; +import { ParseMarkdownDto } from './dto/parse-markdown.dto'; +import { ShareRecipeDto } from './dto/share-recipe.dto'; +import { SetRecipeVisibilityDto } from './dto/set-recipe-visibility.dto'; +declare class UpdateImageDto { + sourceUrl: string; +} +export declare class RecipesController { + private readonly recipesService; + constructor(recipesService: RecipesService); + parseMarkdown(dto: ParseMarkdownDto): Promise<{ + name: string; + description: string; + instructions: string; + ingredients: { + rawName: string; + rawLine: string; + alternatives: string[]; + quantity: number; + unit: string; + note: string | null; + suggestions: { + productId: number; + productName: string; + score: number; + }[]; + }[]; + }>; + getAiSuggestions(user: { + userId: number; + }): Promise<{ + suggestions: import("./recipes.service").AiRecipeSuggestion[]; + }>; + findAll(user: { + userId: number; + }): Promise<({ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + })[]>; + getInventoryPreview(id: number, user: { + userId: number; + }): Promise<{ + recipe: { + id: number; + name: string; + description: string | null; + }; + ingredients: { + ingredientId: any; + productId: any; + productName: any; + requiredQuantity: number; + requiredUnit: any; + note: any; + availableQuantity: number; + availableUnit: any; + matchingInventoryItems: { + id: any; + quantity: any; + unit: any; + location: any; + brand: any; + bestBeforeDate: any; + }[]; + otherInventoryItems: { + id: any; + quantity: any; + unit: any; + location: any; + convertedQuantity: number; + canConvert: boolean; + }[]; + status: "missing" | "enough" | "unit_mismatch"; + fromPantry: boolean; + missingQuantity: number; + }[]; + summary: { + totalIngredients: number; + enoughCount: number; + missingCount: number; + unitMismatchCount: number; + canCookExactly: boolean; + pantryCount: number; + }; + }>; + findOne(id: number, user: { + userId: number; + }): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + create(createRecipeDto: CreateRecipeDto, user: { + userId: number; + }): Promise<{ + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + update(id: number, createRecipeDto: CreateRecipeDto, user: { + userId: number; + }): Promise<{ + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + remove(id: number, user: { + userId: number; + }): Promise; + updateImage(id: number, dto: UpdateImageDto, user: { + userId: number; + }): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + addIngredient(id: number, ingredient: CreateIngredientDto, user: { + userId: number; + }): Promise<{ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + }>; + setVisibility(id: number, dto: SetRecipeVisibilityDto, user: { + userId: number; + }): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + shareRecipe(id: number, dto: ShareRecipeDto, user: { + userId: number; + }): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + unshareRecipe(id: number, username: string, user: { + userId: number; + }): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: import("@prisma/client/runtime/library").Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: import("@prisma/client/runtime/library").JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; +} +export {}; diff --git a/backend/dist/recipes/recipes.controller.js b/backend/dist/recipes/recipes.controller.js new file mode 100644 index 00000000..fb901717 --- /dev/null +++ b/backend/dist/recipes/recipes.controller.js @@ -0,0 +1,188 @@ +"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.RecipesController = void 0; +const common_1 = require("@nestjs/common"); +const class_validator_1 = require("class-validator"); +const recipes_service_1 = require("./recipes.service"); +const create_recipe_dto_1 = require("./dto/create-recipe.dto"); +const create_ingredient_dto_1 = require("./dto/create-ingredient.dto"); +const parse_markdown_dto_1 = require("./dto/parse-markdown.dto"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +const share_recipe_dto_1 = require("./dto/share-recipe.dto"); +const set_recipe_visibility_dto_1 = require("./dto/set-recipe-visibility.dto"); +class UpdateImageDto { +} +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateImageDto.prototype, "sourceUrl", void 0); +let RecipesController = class RecipesController { + constructor(recipesService) { + this.recipesService = recipesService; + } + parseMarkdown(dto) { + return this.recipesService.parseMarkdown(dto); + } + getAiSuggestions(user) { + return this.recipesService.suggestRecipesFromInventory(user.userId); + } + findAll(user) { + return this.recipesService.findAll(user.userId); + } + getInventoryPreview(id, user) { + return this.recipesService.getInventoryPreview(id, user.userId); + } + findOne(id, user) { + return this.recipesService.findOne(id, user.userId); + } + async create(createRecipeDto, user) { + return this.recipesService.create(createRecipeDto, user.userId); + } + async update(id, createRecipeDto, user) { + return this.recipesService.update(id, createRecipeDto, user.userId); + } + async remove(id, user) { + return this.recipesService.remove(id, user.userId); + } + async updateImage(id, dto, user) { + return this.recipesService.updateImage(id, dto.sourceUrl, user.userId); + } + async addIngredient(id, ingredient, user) { + return this.recipesService.addIngredient(id, ingredient, user.userId); + } + async setVisibility(id, dto, user) { + return this.recipesService.setVisibility(id, user.userId, dto.isPublic); + } + async shareRecipe(id, dto, user) { + return this.recipesService.shareWithUser(id, user.userId, dto.username.trim()); + } + async unshareRecipe(id, username, user) { + return this.recipesService.unshareWithUser(id, user.userId, username.trim()); + } +}; +exports.RecipesController = RecipesController; +__decorate([ + (0, common_1.Post)('parse-markdown'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [parse_markdown_dto_1.ParseMarkdownDto]), + __metadata("design:returntype", void 0) +], RecipesController.prototype, "parseMarkdown", null); +__decorate([ + (0, common_1.Get)('ai-suggestions'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], RecipesController.prototype, "getAiSuggestions", null); +__decorate([ + (0, common_1.Get)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], RecipesController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)(':id/inventory-preview'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", void 0) +], RecipesController.prototype, "getInventoryPreview", null); +__decorate([ + (0, common_1.Get)(':id'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", void 0) +], RecipesController.prototype, "findOne", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [create_recipe_dto_1.CreateRecipeDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "create", null); +__decorate([ + (0, common_1.Patch)(':id'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, create_recipe_dto_1.CreateRecipeDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)(':id'), + (0, common_1.HttpCode)(204), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "remove", null); +__decorate([ + (0, common_1.Post)(':id/image'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, UpdateImageDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "updateImage", null); +__decorate([ + (0, common_1.Post)(':id/ingredients'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, create_ingredient_dto_1.CreateIngredientDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "addIngredient", null); +__decorate([ + (0, common_1.Patch)(':id/visibility'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, set_recipe_visibility_dto_1.SetRecipeVisibilityDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "setVisibility", null); +__decorate([ + (0, common_1.Post)(':id/share'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, share_recipe_dto_1.ShareRecipeDto, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "shareRecipe", null); +__decorate([ + (0, common_1.Delete)(':id/share/:username'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Param)('username')), + __param(2, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, String, Object]), + __metadata("design:returntype", Promise) +], RecipesController.prototype, "unshareRecipe", null); +exports.RecipesController = RecipesController = __decorate([ + (0, common_1.Controller)('recipes'), + __metadata("design:paramtypes", [recipes_service_1.RecipesService]) +], RecipesController); +//# sourceMappingURL=recipes.controller.js.map \ No newline at end of file diff --git a/backend/dist/recipes/recipes.controller.js.map b/backend/dist/recipes/recipes.controller.js.map new file mode 100644 index 00000000..b323d1a1 --- /dev/null +++ b/backend/dist/recipes/recipes.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipes.controller.js","sourceRoot":"","sources":["../../src/recipes/recipes.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/recipes/recipes.module.d.ts b/backend/dist/recipes/recipes.module.d.ts new file mode 100644 index 00000000..d137b42a --- /dev/null +++ b/backend/dist/recipes/recipes.module.d.ts @@ -0,0 +1,2 @@ +export declare class RecipesModule { +} diff --git a/backend/dist/recipes/recipes.module.js b/backend/dist/recipes/recipes.module.js new file mode 100644 index 00000000..c00bfbe2 --- /dev/null +++ b/backend/dist/recipes/recipes.module.js @@ -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.RecipesModule = void 0; +const common_1 = require("@nestjs/common"); +const prisma_module_1 = require("../prisma/prisma.module"); +const ai_module_1 = require("../ai/ai.module"); +const recipes_controller_1 = require("./recipes.controller"); +const recipes_service_1 = require("./recipes.service"); +let RecipesModule = class RecipesModule { +}; +exports.RecipesModule = RecipesModule; +exports.RecipesModule = RecipesModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule, ai_module_1.AiModule], + controllers: [recipes_controller_1.RecipesController], + providers: [recipes_service_1.RecipesService], + }) +], RecipesModule); +//# sourceMappingURL=recipes.module.js.map \ No newline at end of file diff --git a/backend/dist/recipes/recipes.module.js.map b/backend/dist/recipes/recipes.module.js.map new file mode 100644 index 00000000..2650eab6 --- /dev/null +++ b/backend/dist/recipes/recipes.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipes.module.js","sourceRoot":"","sources":["../../src/recipes/recipes.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,+CAA2C;AAC3C,6DAAyD;AACzD,uDAAmD;AAO5C,IAAM,aAAa,GAAnB,MAAM,aAAa;CAAG,CAAA;AAAhB,sCAAa;wBAAb,aAAa;IALzB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,EAAE,oBAAQ,CAAC;QACjC,WAAW,EAAE,CAAC,sCAAiB,CAAC;QAChC,SAAS,EAAE,CAAC,gCAAc,CAAC;KAC5B,CAAC;GACW,aAAa,CAAG"} \ No newline at end of file diff --git a/backend/dist/recipes/recipes.service.d.ts b/backend/dist/recipes/recipes.service.d.ts new file mode 100644 index 00000000..50c60398 --- /dev/null +++ b/backend/dist/recipes/recipes.service.d.ts @@ -0,0 +1,632 @@ +import { Prisma } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; +import { AiService } from '../ai/ai.service'; +import { CreateRecipeDto } from './dto/create-recipe.dto'; +import { CreateIngredientDto } from './dto/create-ingredient.dto'; +import { ParseMarkdownDto } from './dto/parse-markdown.dto'; +export interface AiRecipeSuggestion { + name: string; + description: string; + mainIngredients: string[]; + missingIngredients: string[]; + estimatedTime: string; +} +export declare class RecipesService { + private readonly prisma; + private readonly aiService; + private readonly logger; + constructor(prisma: PrismaService, aiService: AiService); + private throwRecipeNotFound; + private assertProductsActive; + private findRecipeByIdOrThrow; + private assertAndClaimRecipeOwner; + private assertRecipeOwnedByUser; + getInventoryPreview(id: number, userId: number): Promise<{ + recipe: { + id: number; + name: string; + description: string | null; + }; + ingredients: { + ingredientId: any; + productId: any; + productName: any; + requiredQuantity: number; + requiredUnit: any; + note: any; + availableQuantity: number; + availableUnit: any; + matchingInventoryItems: { + id: any; + quantity: any; + unit: any; + location: any; + brand: any; + bestBeforeDate: any; + }[]; + otherInventoryItems: { + id: any; + quantity: any; + unit: any; + location: any; + convertedQuantity: number; + canConvert: boolean; + }[]; + status: "missing" | "enough" | "unit_mismatch"; + fromPantry: boolean; + missingQuantity: number; + }[]; + summary: { + totalIngredients: number; + enoughCount: number; + missingCount: number; + unitMismatchCount: number; + canCookExactly: boolean; + pantryCount: number; + }; + }>; + findAll(userId: number): Promise<({ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + })[]>; + findOne(id: number, userId: number): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + update(id: number, updateRecipeDto: CreateRecipeDto, userId: number): Promise<{ + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + remove(id: number, userId: number): Promise; + updateImage(id: number, sourceUrl: string, userId: number): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + setVisibility(id: number, userId: number, isPublic: boolean): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + shareWithUser(id: number, ownerId: number, username: string): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + unshareWithUser(id: number, ownerId: number, username: string): Promise<{ + owner: { + id: number; + username: string; + } | null; + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + shares: { + userId: number; + }[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + create(createRecipeDto: CreateRecipeDto, userId: number): Promise<{ + ingredients: ({ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + })[]; + } & { + isPublic: boolean; + name: string; + id: number; + createdAt: Date; + updatedAt: Date; + ownerId: number | null; + description: string | null; + instructions: string | null; + imageUrl: string | null; + servings: number | null; + }>; + addIngredient(id: number, ingredient: CreateIngredientDto, userId: number): Promise<{ + product: ({ + 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; + } & { + 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; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + productId: number | null; + quantity: Prisma.Decimal | null; + unit: string | null; + rawName: string; + rawLine: string | null; + note: string | null; + matchConfidence: number | null; + matchSource: string | null; + alternativeProductIds: Prisma.JsonValue | null; + recipeId: number; + analysisStatus: string | null; + }>; + suggestRecipesFromInventory(userId: number): Promise<{ + suggestions: AiRecipeSuggestion[]; + }>; + parseMarkdown(dto: ParseMarkdownDto): Promise<{ + name: string; + description: string; + instructions: string; + ingredients: { + rawName: string; + rawLine: string; + alternatives: string[]; + quantity: number; + unit: string; + note: string | null; + suggestions: { + productId: number; + productName: string; + score: number; + }[]; + }[]; + }>; +} diff --git a/backend/dist/recipes/recipes.service.js b/backend/dist/recipes/recipes.service.js new file mode 100644 index 00000000..1ecef83c --- /dev/null +++ b/backend/dist/recipes/recipes.service.js @@ -0,0 +1,682 @@ +"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 RecipesService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RecipesService = void 0; +const common_1 = require("@nestjs/common"); +const fs = require("node:fs/promises"); +const path = require("node:path"); +const prisma_service_1 = require("../prisma/prisma.service"); +const ai_service_1 = require("../ai/ai.service"); +const download_image_1 = require("../common/utils/download-image"); +const recipe_parser_1 = require("../common/utils/recipe-parser"); +const units_1 = require("../common/utils/units"); +const IMAGE_DEST_DIR = process.env.IMAGE_DEST_DIR || '/app/recipe-images'; +let RecipesService = RecipesService_1 = class RecipesService { + constructor(prisma, aiService) { + this.prisma = prisma; + this.aiService = aiService; + this.logger = new common_1.Logger(RecipesService_1.name); + } + throwRecipeNotFound(id) { + throw new common_1.NotFoundException(`Recipe with id ${id} not found`); + } + async assertProductsActive(productIds) { + if (productIds.length === 0) + return; + const activeProducts = await this.prisma.product.findMany({ + where: { id: { in: productIds }, isActive: true }, + select: { id: true }, + }); + if (activeProducts.length !== productIds.length) { + const foundIds = new Set(activeProducts.map((p) => p.id)); + const missing = productIds.filter((id) => !foundIds.has(id)); + throw new common_1.BadRequestException(`En eller flera ingrediensprodukter är inaktiva eller finns inte: ${missing.join(', ')}`); + } + } + async findRecipeByIdOrThrow(id) { + const recipe = await this.prisma.recipe.findUnique({ where: { id } }); + if (!recipe) { + this.throwRecipeNotFound(id); + } + return recipe; + } + async assertAndClaimRecipeOwner(recipe, userId) { + if (recipe.ownerId === null) { + await this.prisma.recipe.update({ + where: { id: recipe.id }, + data: { ownerId: userId }, + }); + } + else if (recipe.ownerId !== userId) { + this.throwRecipeNotFound(recipe.id); + } + } + assertRecipeOwnedByUser(recipe, userId, id) { + if (recipe.ownerId !== userId) { + this.throwRecipeNotFound(id); + } + } + async getInventoryPreview(id, userId) { + const recipe = await this.prisma.recipe.findFirst({ + where: { + id, + OR: [ + { isPublic: true }, + { ownerId: userId }, + { shares: { some: { userId } } }, + ], + }, + include: { + ingredients: { + include: { + product: true, + }, + orderBy: { + id: 'asc', + }, + }, + }, + }); + if (!recipe) { + throw new common_1.NotFoundException(`Recipe with id ${id} not found`); + } + const pantryItems = await this.prisma.pantryItem.findMany({ + where: { userId }, + select: { productId: true }, + }); + const pantryProductIds = new Set(pantryItems.map((p) => p.productId)); + const ingredientPreviews = await Promise.all(recipe.ingredients.map(async (ingredient) => { + if (!ingredient.productId || !ingredient.product) { + return { + ingredientId: ingredient.id, + productId: null, + productName: ingredient.rawName || 'Okänd ingrediens', + requiredQuantity: Number(ingredient.quantity ?? 0), + requiredUnit: ingredient.unit || '', + note: ingredient.note, + availableQuantity: 0, + availableUnit: ingredient.unit || '', + matchingInventoryItems: [], + otherInventoryItems: [], + status: 'missing', + fromPantry: false, + missingQuantity: Number(ingredient.quantity ?? 0), + }; + } + const requiredUnit = (ingredient.unit ?? '').trim(); + const requiredQuantity = Number(ingredient.quantity ?? 0); + const coveredByPantry = pantryProductIds.has(ingredient.productId) || + (Array.isArray(ingredient.alternativeProductIds) && + ingredient.alternativeProductIds.some((altId) => pantryProductIds.has(altId))); + if (coveredByPantry) { + return { + ingredientId: ingredient.id, + productId: ingredient.productId, + productName: ingredient.product.canonicalName || ingredient.product.name, + requiredQuantity, + requiredUnit, + note: ingredient.note, + availableQuantity: requiredQuantity, + availableUnit: requiredUnit, + matchingInventoryItems: [], + otherInventoryItems: [], + status: 'enough', + fromPantry: true, + missingQuantity: 0, + }; + } + const inventoryItems = await this.prisma.inventoryItem.findMany({ + where: { + productId: { + in: [ + ingredient.productId, + ...(Array.isArray(ingredient.alternativeProductIds) + ? ingredient.alternativeProductIds + : []), + ], + }, + }, + orderBy: { createdAt: 'desc' }, + }); + const sameUnitItems = inventoryItems.filter((item) => requiredUnit + ? item.unit.trim().toLowerCase() === requiredUnit.toLowerCase() + : true); + const availableSameUnit = sameUnitItems.reduce((sum, item) => sum + Number(item.quantity), 0); + const otherUnitItems = inventoryItems.filter((item) => requiredUnit + ? item.unit.trim().toLowerCase() !== requiredUnit.toLowerCase() + : false); + let availableOtherUnit = 0; + for (const item of otherUnitItems) { + try { + const convertedQuantity = (0, units_1.convertUnit)(Number(item.quantity), item.unit, requiredUnit); + availableOtherUnit += convertedQuantity; + } + catch { + } + } + const totalAvailable = availableSameUnit + availableOtherUnit; + let status; + if (totalAvailable >= requiredQuantity) { + status = 'enough'; + } + else if (availableSameUnit === 0 && availableOtherUnit > 0) { + status = 'unit_mismatch'; + } + else { + status = 'missing'; + } + return { + ingredientId: ingredient.id, + productId: ingredient.productId, + productName: ingredient.product.canonicalName || ingredient.product.name, + requiredQuantity, + requiredUnit, + note: ingredient.note, + availableQuantity: totalAvailable, + availableUnit: requiredUnit, + matchingInventoryItems: sameUnitItems.map((item) => ({ + id: item.id, + quantity: item.quantity, + unit: item.unit, + location: item.location, + brand: item.brand || null, + bestBeforeDate: item.bestBeforeDate || null, + })), + otherInventoryItems: otherUnitItems.map((item) => { + const canConvertUnits = requiredUnit ? (0, units_1.canConvert)(item.unit, requiredUnit) : false; + let convertedQuantity = 0; + if (canConvertUnits) { + try { + convertedQuantity = (0, units_1.convertUnit)(Number(item.quantity), item.unit, requiredUnit); + } + catch { + convertedQuantity = 0; + } + } + return { + id: item.id, + quantity: item.quantity, + unit: item.unit, + location: item.location, + convertedQuantity: canConvertUnits ? convertedQuantity : 0, + canConvert: canConvertUnits, + }; + }), + status, + fromPantry: false, + missingQuantity: status === 'missing' ? Math.max(0, requiredQuantity - totalAvailable) : 0, + }; + })); + const summary = { + totalIngredients: ingredientPreviews.length, + enoughCount: ingredientPreviews.filter((i) => i.status === 'enough').length, + missingCount: ingredientPreviews.filter((i) => i.status === 'missing').length, + unitMismatchCount: ingredientPreviews.filter((i) => i.status === 'unit_mismatch').length, + canCookExactly: ingredientPreviews.every((i) => i.status === 'enough'), + pantryCount: ingredientPreviews.filter((i) => i.fromPantry).length, + }; + return { + recipe: { + id: recipe.id, + name: recipe.name, + description: recipe.description, + }, + ingredients: ingredientPreviews, + summary, + }; + } + async findAll(userId) { + return this.prisma.recipe.findMany({ + where: { + OR: [ + { isPublic: true }, + { ownerId: userId }, + { shares: { some: { userId } } }, + ], + }, + include: { + ingredients: { + include: { + product: { include: { nutrition: true } }, + }, + }, + owner: { select: { id: true, username: true } }, + shares: { select: { userId: true } }, + }, + }); + } + async findOne(id, userId) { + const recipe = await this.prisma.recipe.findFirst({ + where: { + id, + OR: [ + { isPublic: true }, + { ownerId: userId }, + { shares: { some: { userId } } }, + ], + }, + include: { + ingredients: { + include: { + product: { include: { nutrition: true } }, + }, + }, + owner: { select: { id: true, username: true } }, + shares: { select: { userId: true } }, + }, + }); + if (!recipe) { + throw new common_1.NotFoundException(`Recipe with id ${id} not found`); + } + return recipe; + } + async update(id, updateRecipeDto, userId) { + const existingRecipe = await this.findRecipeByIdOrThrow(id); + await this.assertAndClaimRecipeOwner(existingRecipe, userId); + await this.assertProductsActive(updateRecipeDto.ingredients + .map((i) => i.productId) + .filter((id) => typeof id === 'number')); + const recipe = await this.prisma.$transaction(async (tx) => { + await tx.recipeIngredient.deleteMany({ where: { recipeId: id } }); + return tx.recipe.update({ + where: { id }, + data: { + name: updateRecipeDto.name, + description: updateRecipeDto.description || null, + instructions: updateRecipeDto.instructions || null, + servings: updateRecipeDto.servings ?? null, + ...(updateRecipeDto.isPublic !== undefined && { isPublic: updateRecipeDto.isPublic }), + ...(updateRecipeDto.imageUrl !== undefined && { imageUrl: updateRecipeDto.imageUrl || null }), + ingredients: { + create: updateRecipeDto.ingredients.map((ingredient) => ({ + productId: ingredient.productId ?? null, + rawName: ingredient.rawName, + rawLine: ingredient.rawLine ?? null, + quantity: ingredient.quantity ?? null, + unit: ingredient.unit?.trim() ? ingredient.unit : null, + note: ingredient.note || null, + alternativeProductIds: ingredient.alternativeProductIds ?? [], + matchConfidence: ingredient.matchConfidence ?? null, + matchSource: ingredient.matchSource ?? null, + })), + }, + }, + include: { + ingredients: { + include: { + product: { include: { nutrition: true } }, + }, + }, + }, + }); + }); + return recipe; + } + async remove(id, userId) { + const existingRecipe = await this.findRecipeByIdOrThrow(id); + await this.assertAndClaimRecipeOwner(existingRecipe, userId); + await this.prisma.recipeIngredient.deleteMany({ where: { recipeId: id } }); + await this.prisma.recipe.delete({ where: { id } }); + if (existingRecipe.imageUrl?.startsWith('/images/')) { + const filename = path.basename(existingRecipe.imageUrl); + const filePath = path.join(IMAGE_DEST_DIR, filename); + await fs.unlink(filePath).catch(() => { + }); + } + } + async updateImage(id, sourceUrl, userId) { + const existingRecipe = await this.findRecipeByIdOrThrow(id); + this.assertRecipeOwnedByUser(existingRecipe, userId, id); + const imageUrl = await (0, download_image_1.downloadAndOptimizeImage)(sourceUrl, IMAGE_DEST_DIR); + return this.prisma.recipe.update({ + where: { id }, + data: { imageUrl }, + include: { + ingredients: { include: { product: { include: { nutrition: true } } } }, + owner: { select: { id: true, username: true } }, + shares: { select: { userId: true } }, + }, + }); + } + async setVisibility(id, userId, isPublic) { + const existingRecipe = await this.findRecipeByIdOrThrow(id); + this.assertRecipeOwnedByUser(existingRecipe, userId, id); + if (isPublic) { + const owner = await this.prisma.user.findUnique({ + where: { id: userId }, + select: { canShareRecipes: true }, + }); + if (!owner?.canShareRecipes) { + throw new common_1.ForbiddenException('Du har inte behörighet att dela recept.'); + } + } + return this.prisma.recipe.update({ + where: { id }, + data: { isPublic }, + include: { + ingredients: { include: { product: { include: { nutrition: true } } } }, + owner: { select: { id: true, username: true } }, + shares: { select: { userId: true } }, + }, + }); + } + async shareWithUser(id, ownerId, username) { + const recipe = await this.findRecipeByIdOrThrow(id); + this.assertRecipeOwnedByUser(recipe, ownerId, id); + const owner = await this.prisma.user.findUnique({ + where: { id: ownerId }, + select: { canShareRecipes: true }, + }); + if (!owner?.canShareRecipes) { + throw new common_1.ForbiddenException('Du har inte behörighet att dela recept.'); + } + const targetUser = await this.prisma.user.findUnique({ + where: { username }, + select: { id: true }, + }); + if (!targetUser) { + throw new common_1.NotFoundException(`User ${username} not found`); + } + if (targetUser.id === ownerId) { + return this.findOne(id, ownerId); + } + await this.prisma.recipeShare.upsert({ + where: { recipeId_userId: { recipeId: id, userId: targetUser.id } }, + create: { recipeId: id, userId: targetUser.id }, + update: {}, + }); + return this.findOne(id, ownerId); + } + async unshareWithUser(id, ownerId, username) { + const recipe = await this.findRecipeByIdOrThrow(id); + this.assertRecipeOwnedByUser(recipe, ownerId, id); + const targetUser = await this.prisma.user.findUnique({ + where: { username }, + select: { id: true }, + }); + if (!targetUser) { + throw new common_1.NotFoundException(`User ${username} not found`); + } + await this.prisma.recipeShare.deleteMany({ + where: { recipeId: id, userId: targetUser.id }, + }); + return this.findOne(id, ownerId); + } + async create(createRecipeDto, userId) { + await this.assertProductsActive(createRecipeDto.ingredients + .map((i) => i.productId) + .filter((id) => typeof id === 'number')); + this.logger.log(`[create] Incoming imageUrl from client: ${createRecipeDto.imageUrl ?? 'null'}`); + let imageUrl = createRecipeDto.imageUrl || null; + let downloadedImagePath = null; + if (imageUrl && imageUrl.startsWith('http')) { + const externalImageUrl = imageUrl; + try { + imageUrl = await (0, download_image_1.downloadAndOptimizeImage)(imageUrl, IMAGE_DEST_DIR); + downloadedImagePath = imageUrl; + } + catch (err) { + console.warn('[RecipesService] Kunde inte ladda ner receptbild:', err); + imageUrl = externalImageUrl; + } + } + this.logger.log(`[create] Final imageUrl persisted to DB: ${imageUrl ?? 'null'}`); + try { + const recipe = await this.prisma.recipe.create({ + data: { + name: createRecipeDto.name, + description: createRecipeDto.description || null, + instructions: createRecipeDto.instructions || null, + imageUrl, + servings: createRecipeDto.servings ?? null, + ownerId: userId, + isPublic: false, + ingredients: { + create: createRecipeDto.ingredients.map((ingredient) => ({ + productId: ingredient.productId ?? null, + rawName: ingredient.rawName, + rawLine: ingredient.rawLine ?? null, + quantity: ingredient.quantity ?? null, + unit: ingredient.unit?.trim() ? ingredient.unit : null, + note: ingredient.note || null, + alternativeProductIds: ingredient.alternativeProductIds ?? [], + matchConfidence: ingredient.matchConfidence ?? null, + matchSource: ingredient.matchSource ?? null, + })), + }, + }, + include: { + ingredients: { + include: { + product: { include: { nutrition: true } }, + }, + }, + }, + }); + return recipe; + } + catch (err) { + if (downloadedImagePath) { + await fs.unlink(path.join(IMAGE_DEST_DIR, path.basename(downloadedImagePath))).catch(() => { }); + } + throw err; + } + } + async addIngredient(id, ingredient, userId) { + const recipe = await this.findRecipeByIdOrThrow(id); + await this.assertRecipeOwnedByUser(recipe, userId, id); + await this.assertProductsActive([ingredient.productId]); + return this.prisma.recipeIngredient.create({ + data: { + productId: ingredient.productId, + quantity: ingredient.quantity, + unit: ingredient.unit, + note: ingredient.note || null, + recipeId: id, + }, + include: { + product: { include: { nutrition: true } }, + }, + }); + } + async suggestRecipesFromInventory(userId) { + const inventoryItems = await this.prisma.inventoryItem.findMany({ + include: { product: { select: { canonicalName: true, name: true } } }, + orderBy: { bestBeforeDate: 'asc' }, + }); + const pantryItems = await this.prisma.pantryItem.findMany({ + where: { userId }, + include: { product: { select: { canonicalName: true, name: true } } }, + }); + if (inventoryItems.length === 0 && pantryItems.length === 0) { + return { suggestions: [] }; + } + const inventoryLines = inventoryItems.map((item) => { + const name = item.product.canonicalName || item.product.name; + return `- ${item.quantity} ${item.unit} ${name}`; + }); + const pantryLines = pantryItems.map((item) => { + const name = item.product.canonicalName || item.product.name; + return `- ${name} (stapelvara, alltid tillgänglig)`; + }); + const ingredientSummary = [ + inventoryLines.length > 0 ? 'Jag har följande i kylen/skafferiet:' : '', + ...inventoryLines, + pantryLines.length > 0 ? '\nStapelvaror (alltid tillgängliga):' : '', + ...pantryLines, + ] + .filter(Boolean) + .join('\n'); + const apiKey = process.env.MISTRAL_API_KEY; + if (!apiKey) { + this.logger.warn('MISTRAL_API_KEY saknas — kan inte generera receptförslag'); + return { suggestions: [] }; + } + const systemPrompt = `Du är en hjälpsam matlagningsassistent för en svensk livsmedelsapp. +Din uppgift är att föreslå recept baserat på vad användaren har hemma. + +Regler: +1. Föreslå 3-5 recept som kan lagas med de tillgängliga ingredienserna. +2. Recepten ska vara realistiska och genomförbara. +3. Det är OK om några få vanliga ingredienser saknas (t.ex. salt, olja, kryddor). +4. Svara ENDAST med giltig JSON i detta exakta format: +{ + "suggestions": [ + { + "name": "Receptnamn", + "description": "Kort beskrivning på 1-2 meningar", + "mainIngredients": ["ingrediens1", "ingrediens2", "ingrediens3"], + "missingIngredients": ["eventuellt saknad ingrediens"], + "estimatedTime": "30 min" + } + ] +}`; + const userPrompt = ingredientSummary; + let raw = ''; + try { + const response = await fetch('https://api.mistral.ai/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: 'mistral-small-latest', + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt }, + ], + max_tokens: 1500, + temperature: 0.7, + response_format: { type: 'json_object' }, + }), + }); + if (!response.ok) { + this.logger.error(`Mistral API-fel vid receptförslag: ${response.status}`); + return { suggestions: [] }; + } + const data = await response.json(); + raw = data.choices?.[0]?.message?.content ?? '{}'; + } + catch (err) { + this.logger.error(`Kunde inte nå Mistral för receptförslag: ${err}`); + return { suggestions: [] }; + } + try { + const parsed = JSON.parse(raw); + return { suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions : [] }; + } + catch { + this.logger.error(`Kunde inte parsa AI-svar för receptförslag: ${raw}`); + return { suggestions: [] }; + } + } + async parseMarkdown(dto) { + const importerUrl = process.env.IMPORTER_SERVICE_URL || 'http://importer-api:3001'; + let parsed; + try { + const response = await fetch(`${importerUrl}/api/recipes/parse-markdown`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ markdown: dto.markdown }), + }); + if (!response.ok) { + throw new Error(`Importer svarade ${response.status}`); + } + parsed = (await response.json()); + } + catch (err) { + this.logger.error(`Kunde inte nå importer-api för parse-markdown: ${err}`); + parsed = (0, recipe_parser_1.parseRecipeMarkdown)(dto.markdown); + } + const allProducts = await this.prisma.product.findMany({ + where: { isActive: true }, + select: { id: true, name: true, canonicalName: true, normalizedName: true }, + }); + const normalize = (s) => s.toLowerCase().trim().replace(/[^a-zåäö0-9\s]/gi, '').replace(/\s+/g, ' '); + const levenshtein = (a, b) => { + const m = a.length; + const n = b.length; + const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))); + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + dp[i][j] = + a[i - 1] === b[j - 1] + ? dp[i - 1][j - 1] + : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]); + } + } + return dp[m][n]; + }; + const ingredientsWithSuggestions = parsed.ingredients.map((ingredient) => { + const alternatives = ingredient.alternatives?.length > 1 + ? ingredient.alternatives + : [ingredient.rawName]; + const scoreProduct = (query) => allProducts + .map((product) => { + const targetName = normalize(product.canonicalName || product.name); + const targetNormalized = normalize(product.normalizedName); + if (targetNormalized === query || targetName === query) { + return { product, score: 100 }; + } + if (targetName.includes(query) || query.includes(targetName)) { + return { product, score: 70 }; + } + const dist = levenshtein(query, targetName); + const maxLen = Math.max(query.length, targetName.length); + const similarity = maxLen === 0 ? 100 : Math.round((1 - dist / maxLen) * 100); + return { product, score: similarity }; + }) + .filter((s) => s.score >= 40) + .sort((a, b) => b.score - a.score) + .slice(0, 5); + const seenIds = new Set(); + const scored = alternatives + .flatMap((alt) => scoreProduct(normalize(alt))) + .filter((s) => { + if (seenIds.has(s.product.id)) + return false; + seenIds.add(s.product.id); + return true; + }) + .sort((a, b) => b.score - a.score) + .slice(0, 5) + .map((s) => ({ + productId: s.product.id, + productName: s.product.canonicalName || s.product.name, + score: s.score, + })); + return { + rawName: ingredient.rawName, + rawLine: ingredient.rawName, + alternatives: ingredient.alternatives ?? [], + quantity: ingredient.quantity, + unit: ingredient.unit, + note: ingredient.note, + suggestions: scored, + }; + }); + return { + name: parsed.name, + description: parsed.description, + instructions: parsed.instructions, + ingredients: ingredientsWithSuggestions, + }; + } +}; +exports.RecipesService = RecipesService; +exports.RecipesService = RecipesService = RecipesService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + ai_service_1.AiService]) +], RecipesService); +//# sourceMappingURL=recipes.service.js.map \ No newline at end of file diff --git a/backend/dist/recipes/recipes.service.js.map b/backend/dist/recipes/recipes.service.js.map new file mode 100644 index 00000000..a3be2eef --- /dev/null +++ b/backend/dist/recipes/recipes.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipes.service.js","sourceRoot":"","sources":["../../src/recipes/recipes.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/recipes/recipes.service.spec.d.ts b/backend/dist/recipes/recipes.service.spec.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/backend/dist/recipes/recipes.service.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/backend/dist/recipes/recipes.service.spec.js b/backend/dist/recipes/recipes.service.spec.js new file mode 100644 index 00000000..2ee3d441 --- /dev/null +++ b/backend/dist/recipes/recipes.service.spec.js @@ -0,0 +1,77 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const units_1 = require("../common/utils/units"); +const convert = (q, from, to) => (0, units_1.convertUnit)(q, from, to); +const normalize = (u) => (0, units_1.normalizeUnit)(u); +describe('normalizeUnit', () => { + it('normaliserar aliaser', () => { + expect(normalize('tesked')).toBe('tsk'); + expect(normalize('matsked')).toBe('msk'); + expect(normalize('gram')).toBe('g'); + expect(normalize('kilogram')).toBe('kg'); + expect(normalize('deciliter')).toBe('dl'); + expect(normalize('milliliter')).toBe('ml'); + }); + it('hanterar gemener och blanksteg', () => { + expect(normalize(' MSK ')).toBe('msk'); + expect(normalize('G')).toBe('g'); + }); + it('returnerar okänd enhet oförändrad', () => { + expect(normalize('kopp')).toBe('kopp'); + }); +}); +describe('convertUnit', () => { + describe('viktkonvertering', () => { + it('konverterar g → kg', () => { + expect(convert(500, 'g', 'kg')).toBeCloseTo(0.5); + }); + it('konverterar kg → g', () => { + expect(convert(1.5, 'kg', 'g')).toBeCloseTo(1500); + }); + it('returnerar samma värde för identiska enheter', () => { + expect(convert(200, 'g', 'g')).toBe(200); + }); + }); + describe('volymkonvertering', () => { + it('konverterar dl → ml', () => { + expect(convert(2, 'dl', 'ml')).toBeCloseTo(200); + }); + it('konverterar ml → dl', () => { + expect(convert(150, 'ml', 'dl')).toBeCloseTo(1.5); + }); + }); + describe('portionskonvertering', () => { + it('konverterar msk → tsk (1 msk ≈ 3 tsk)', () => { + expect(convert(2, 'msk', 'tsk')).toBeCloseTo(6); + }); + it('konverterar tsk → msk', () => { + expect(convert(3, 'tsk', 'msk')).toBeCloseTo(1); + }); + }); + describe('normaliserar aliaser vid konvertering', () => { + it('konverterar "gram" → "kg"', () => { + expect(convert(1000, 'gram', 'kg')).toBeCloseTo(1); + }); + it('konverterar "matsked" → "tsk"', () => { + expect(convert(1, 'matsked', 'tsk')).toBeCloseTo(3); + }); + }); + describe('felhantering', () => { + it('kastar fel för inkompatibla enhetstyper', () => { + expect(() => convert(100, 'g', 'dl')).toThrow(); + }); + it('kastar fel för noll-kvantitet', () => { + expect(() => convert(0, 'g', 'kg')).toThrow(); + }); + it('kastar fel för negativ kvantitet', () => { + expect(() => convert(-1, 'g', 'kg')).toThrow(); + }); + it('kastar fel för tom from-enhet', () => { + expect(() => convert(100, '', 'kg')).toThrow(); + }); + it('kastar fel för okänd enhet', () => { + expect(() => convert(100, 'kopp', 'dl')).toThrow(); + }); + }); +}); +//# sourceMappingURL=recipes.service.spec.js.map \ No newline at end of file diff --git a/backend/dist/recipes/recipes.service.spec.js.map b/backend/dist/recipes/recipes.service.spec.js.map new file mode 100644 index 00000000..13904458 --- /dev/null +++ b/backend/dist/recipes/recipes.service.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"recipes.service.spec.js","sourceRoot":"","sources":["../../src/recipes/recipes.service.spec.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/user-products/dto/upsert-user-product.dto.d.ts b/backend/dist/user-products/dto/upsert-user-product.dto.d.ts new file mode 100644 index 00000000..9e8369a4 --- /dev/null +++ b/backend/dist/user-products/dto/upsert-user-product.dto.d.ts @@ -0,0 +1,7 @@ +export declare class UpsertUserProductDto { + productId: number; + note?: string; + preferredBrand?: string; + preferredStore?: string; + isPrivate?: boolean; +} diff --git a/backend/dist/user-products/dto/upsert-user-product.dto.js b/backend/dist/user-products/dto/upsert-user-product.dto.js new file mode 100644 index 00000000..03be01be --- /dev/null +++ b/backend/dist/user-products/dto/upsert-user-product.dto.js @@ -0,0 +1,41 @@ +"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.UpsertUserProductDto = void 0; +const class_validator_1 = require("class-validator"); +class UpsertUserProductDto { +} +exports.UpsertUserProductDto = UpsertUserProductDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpsertUserProductDto.prototype, "productId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpsertUserProductDto.prototype, "note", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpsertUserProductDto.prototype, "preferredBrand", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpsertUserProductDto.prototype, "preferredStore", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], UpsertUserProductDto.prototype, "isPrivate", void 0); +//# sourceMappingURL=upsert-user-product.dto.js.map \ No newline at end of file diff --git a/backend/dist/user-products/dto/upsert-user-product.dto.js.map b/backend/dist/user-products/dto/upsert-user-product.dto.js.map new file mode 100644 index 00000000..19e5be1e --- /dev/null +++ b/backend/dist/user-products/dto/upsert-user-product.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upsert-user-product.dto.js","sourceRoot":"","sources":["../../../src/user-products/dto/upsert-user-product.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAyE;AAEzE,MAAa,oBAAoB;CAmBhC;AAnBD,oDAmBC;AAjBC;IADC,IAAA,uBAAK,GAAE;;uDACU;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;kDACG;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;4DACa;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;4DACa;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;uDACQ"} \ No newline at end of file diff --git a/backend/dist/user-products/user-products.controller.d.ts b/backend/dist/user-products/user-products.controller.d.ts new file mode 100644 index 00000000..7a999eff --- /dev/null +++ b/backend/dist/user-products/user-products.controller.d.ts @@ -0,0 +1,119 @@ +import { UserProductsService } from './user-products.service'; +import { UpsertUserProductDto } from './dto/upsert-user-product.dto'; +export declare class UserProductsController { + private readonly service; + constructor(service: UserProductsService); + findAll(user: { + userId: number; + }): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + })[]>; + upsert(user: { + userId: number; + }, dto: UpsertUserProductDto): import(".prisma/client").Prisma.Prisma__UserProductClient<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + remove(user: { + userId: number; + }, productId: number): import(".prisma/client").Prisma.Prisma__UserProductClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; +} diff --git a/backend/dist/user-products/user-products.controller.js b/backend/dist/user-products/user-products.controller.js new file mode 100644 index 00000000..4b4be9f2 --- /dev/null +++ b/backend/dist/user-products/user-products.controller.js @@ -0,0 +1,62 @@ +"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.UserProductsController = void 0; +const common_1 = require("@nestjs/common"); +const user_products_service_1 = require("./user-products.service"); +const upsert_user_product_dto_1 = require("./dto/upsert-user-product.dto"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +let UserProductsController = class UserProductsController { + constructor(service) { + this.service = service; + } + findAll(user) { + return this.service.findAll(user.userId); + } + upsert(user, dto) { + return this.service.upsert(user.userId, dto); + } + remove(user, productId) { + return this.service.remove(user.userId, productId); + } +}; +exports.UserProductsController = UserProductsController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], UserProductsController.prototype, "findAll", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, upsert_user_product_dto_1.UpsertUserProductDto]), + __metadata("design:returntype", void 0) +], UserProductsController.prototype, "upsert", null); +__decorate([ + (0, common_1.Delete)(':productId'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Param)('productId', common_1.ParseIntPipe)), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Number]), + __metadata("design:returntype", void 0) +], UserProductsController.prototype, "remove", null); +exports.UserProductsController = UserProductsController = __decorate([ + (0, common_1.Controller)('user-products'), + __metadata("design:paramtypes", [user_products_service_1.UserProductsService]) +], UserProductsController); +//# sourceMappingURL=user-products.controller.js.map \ No newline at end of file diff --git a/backend/dist/user-products/user-products.controller.js.map b/backend/dist/user-products/user-products.controller.js.map new file mode 100644 index 00000000..bf3cdf66 --- /dev/null +++ b/backend/dist/user-products/user-products.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"user-products.controller.js","sourceRoot":"","sources":["../../src/user-products/user-products.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAQwB;AACxB,mEAA8D;AAC9D,2EAAqE;AACrE,sFAAwE;AAGjE,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IACjC,YAA6B,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;IAAG,CAAC;IAG7D,OAAO,CAAgB,IAAwB;QAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAGD,MAAM,CACW,IAAwB,EAC/B,GAAyB;QAEjC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IAGD,MAAM,CACW,IAAwB,EACL,SAAiB;QAEnD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACrD,CAAC;CACF,CAAA;AAvBY,wDAAsB;AAIjC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;qDAErB;AAGD;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,8CAAoB;;oDAGlC;AAGD;IADC,IAAA,eAAM,EAAC,YAAY,CAAC;IAElB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,WAAW,EAAE,qBAAY,CAAC,CAAA;;;;oDAGlC;iCAtBU,sBAAsB;IADlC,IAAA,mBAAU,EAAC,eAAe,CAAC;qCAEY,2CAAmB;GAD9C,sBAAsB,CAuBlC"} \ No newline at end of file diff --git a/backend/dist/user-products/user-products.module.d.ts b/backend/dist/user-products/user-products.module.d.ts new file mode 100644 index 00000000..f9a5e313 --- /dev/null +++ b/backend/dist/user-products/user-products.module.d.ts @@ -0,0 +1,2 @@ +export declare class UserProductsModule { +} diff --git a/backend/dist/user-products/user-products.module.js b/backend/dist/user-products/user-products.module.js new file mode 100644 index 00000000..c6d1eb92 --- /dev/null +++ b/backend/dist/user-products/user-products.module.js @@ -0,0 +1,24 @@ +"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.UserProductsModule = void 0; +const common_1 = require("@nestjs/common"); +const user_products_controller_1 = require("./user-products.controller"); +const user_products_service_1 = require("./user-products.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let UserProductsModule = class UserProductsModule { +}; +exports.UserProductsModule = UserProductsModule; +exports.UserProductsModule = UserProductsModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [user_products_controller_1.UserProductsController], + providers: [user_products_service_1.UserProductsService], + }) +], UserProductsModule); +//# sourceMappingURL=user-products.module.js.map \ No newline at end of file diff --git a/backend/dist/user-products/user-products.module.js.map b/backend/dist/user-products/user-products.module.js.map new file mode 100644 index 00000000..ef034a95 --- /dev/null +++ b/backend/dist/user-products/user-products.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"user-products.module.js","sourceRoot":"","sources":["../../src/user-products/user-products.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yEAAoE;AACpE,mEAA8D;AAC9D,2DAAuD;AAOhD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAL9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,iDAAsB,CAAC;QACrC,SAAS,EAAE,CAAC,2CAAmB,CAAC;KACjC,CAAC;GACW,kBAAkB,CAAG"} \ No newline at end of file diff --git a/backend/dist/user-products/user-products.service.d.ts b/backend/dist/user-products/user-products.service.d.ts new file mode 100644 index 00000000..091cfc57 --- /dev/null +++ b/backend/dist/user-products/user-products.service.d.ts @@ -0,0 +1,161 @@ +import { PrismaService } from '../prisma/prisma.service'; +import { UpsertUserProductDto } from './dto/upsert-user-product.dto'; +export declare class UserProductsService { + private readonly prisma; + constructor(prisma: PrismaService); + findAll(userId: number): import(".prisma/client").Prisma.PrismaPromise<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + })[]>; + findOne(userId: number, productId: number): import(".prisma/client").Prisma.Prisma__UserProductClient<({ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + }) | null, null, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + upsert(userId: number, dto: UpsertUserProductDto): import(".prisma/client").Prisma.Prisma__UserProductClient<{ + product: { + 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; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + remove(userId: number, productId: number): import(".prisma/client").Prisma.Prisma__UserProductClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + isPrivate: boolean; + productId: number; + note: string | null; + userId: number; + preferredBrand: string | null; + preferredStore: string | null; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; +} diff --git a/backend/dist/user-products/user-products.service.js b/backend/dist/user-products/user-products.service.js new file mode 100644 index 00000000..c3830a4f --- /dev/null +++ b/backend/dist/user-products/user-products.service.js @@ -0,0 +1,60 @@ +"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.UserProductsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const PRODUCT_INCLUDE = { + product: { + include: { + nutrition: true, + tags: { include: { tag: true } }, + }, + }, +}; +let UserProductsService = class UserProductsService { + constructor(prisma) { + this.prisma = prisma; + } + findAll(userId) { + return this.prisma.userProduct.findMany({ + where: { userId }, + include: PRODUCT_INCLUDE, + orderBy: { updatedAt: 'desc' }, + }); + } + findOne(userId, productId) { + return this.prisma.userProduct.findUnique({ + where: { userId_productId: { userId, productId } }, + include: PRODUCT_INCLUDE, + }); + } + upsert(userId, dto) { + const { productId, ...data } = dto; + return this.prisma.userProduct.upsert({ + where: { userId_productId: { userId, productId } }, + create: { userId, productId, ...data }, + update: data, + include: PRODUCT_INCLUDE, + }); + } + remove(userId, productId) { + return this.prisma.userProduct.delete({ + where: { userId_productId: { userId, productId } }, + }); + } +}; +exports.UserProductsService = UserProductsService; +exports.UserProductsService = UserProductsService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], UserProductsService); +//# sourceMappingURL=user-products.service.js.map \ No newline at end of file diff --git a/backend/dist/user-products/user-products.service.js.map b/backend/dist/user-products/user-products.service.js.map new file mode 100644 index 00000000..370b2a24 --- /dev/null +++ b/backend/dist/user-products/user-products.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"user-products.service.js","sourceRoot":"","sources":["../../src/user-products/user-products.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAGzD,MAAM,eAAe,GAAG;IACtB,OAAO,EAAE;QACP,OAAO,EAAE;YACP,SAAS,EAAE,IAAI;YACf,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;SACjC;KACF;CACF,CAAC;AAGK,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC;YACtC,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,OAAO,EAAE,eAAe;YACxB,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,MAAc,EAAE,SAAiB;QACvC,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC;YACxC,KAAK,EAAE,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;YAClD,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,GAAyB;QAC9C,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;QACnC,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YACpC,KAAK,EAAE,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;YAClD,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE;YACtC,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,SAAiB;QACtC,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;YACpC,KAAK,EAAE,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;SACnD,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAjCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,mBAAmB,CAiC/B"} \ No newline at end of file diff --git a/backend/dist/users/admin-bootstrap.service.d.ts b/backend/dist/users/admin-bootstrap.service.d.ts new file mode 100644 index 00000000..5ef92cdf --- /dev/null +++ b/backend/dist/users/admin-bootstrap.service.d.ts @@ -0,0 +1,8 @@ +import { OnApplicationBootstrap } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +export declare class AdminBootstrapService implements OnApplicationBootstrap { + private readonly prisma; + private readonly logger; + constructor(prisma: PrismaService); + onApplicationBootstrap(): Promise; +} diff --git a/backend/dist/users/admin-bootstrap.service.js b/backend/dist/users/admin-bootstrap.service.js new file mode 100644 index 00000000..2dbb8996 --- /dev/null +++ b/backend/dist/users/admin-bootstrap.service.js @@ -0,0 +1,62 @@ +"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 AdminBootstrapService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminBootstrapService = void 0; +const common_1 = require("@nestjs/common"); +const bcrypt = require("bcryptjs"); +const prisma_service_1 = require("../prisma/prisma.service"); +const SEED_USERS = [ + { username: 'Nadmin', email: 'nadmin@localhost', passwordEnvKey: 'ADMIN_NADMIN_PASSWORD', role: 'admin' }, + { username: 'Padmin', email: 'padmin@localhost', passwordEnvKey: 'ADMIN_PADMIN_PASSWORD', role: 'admin' }, + { username: 'user1', email: 'user1@localhost', passwordEnvKey: 'SEED_USER1_PASSWORD', role: 'user' }, + { username: 'user2', email: 'user2@localhost', passwordEnvKey: 'SEED_USER2_PASSWORD', role: 'user' }, +]; +let AdminBootstrapService = AdminBootstrapService_1 = class AdminBootstrapService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(AdminBootstrapService_1.name); + } + async onApplicationBootstrap() { + for (const seed of SEED_USERS) { + const password = process.env[seed.passwordEnvKey]; + if (!password) { + this.logger.warn(`${seed.passwordEnvKey} not set — skipping ${seed.username}`); + continue; + } + const existing = await this.prisma.user.findUnique({ where: { username: seed.username } }); + if (existing) { + if (existing.role !== seed.role) { + await this.prisma.user.update({ where: { id: existing.id }, data: { role: seed.role } }); + this.logger.log(`Updated role for ${seed.username} → ${seed.role}`); + } + } + else { + const passwordHash = await bcrypt.hash(password, 12); + await this.prisma.user.create({ + data: { + username: seed.username, + email: seed.email, + passwordHash, + role: seed.role, + }, + }); + this.logger.log(`Created ${seed.role} user: ${seed.username}`); + } + } + } +}; +exports.AdminBootstrapService = AdminBootstrapService; +exports.AdminBootstrapService = AdminBootstrapService = AdminBootstrapService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], AdminBootstrapService); +//# sourceMappingURL=admin-bootstrap.service.js.map \ No newline at end of file diff --git a/backend/dist/users/admin-bootstrap.service.js.map b/backend/dist/users/admin-bootstrap.service.js.map new file mode 100644 index 00000000..f86b9ad6 --- /dev/null +++ b/backend/dist/users/admin-bootstrap.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin-bootstrap.service.js","sourceRoot":"","sources":["../../src/users/admin-bootstrap.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA4E;AAC5E,mCAAmC;AACnC,6DAAyD;AASzD,MAAM,UAAU,GAAe;IAC7B,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE;IACzG,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE;IACzG,EAAE,QAAQ,EAAE,OAAO,EAAG,KAAK,EAAE,iBAAiB,EAAG,cAAc,EAAE,qBAAqB,EAAI,IAAI,EAAE,MAAM,EAAE;IACxG,EAAE,QAAQ,EAAE,OAAO,EAAG,KAAK,EAAE,iBAAiB,EAAG,cAAc,EAAE,qBAAqB,EAAI,IAAI,EAAE,MAAM,EAAE;CACzG,CAAC;AAGK,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAGhC,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QAFjC,WAAM,GAAG,IAAI,eAAM,CAAC,uBAAqB,CAAC,IAAI,CAAC,CAAC;IAEZ,CAAC;IAEtD,KAAK,CAAC,sBAAsB;QAC1B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,uBAAuB,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC/E,SAAS;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAE3F,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;oBAChC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBACzF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;gBACrD,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;oBAC5B,IAAI,EAAE;wBACJ,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,KAAK,EAAE,IAAI,CAAC,KAAK;wBACjB,YAAY;wBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;qBAChB;iBACF,CAAC,CAAC;gBACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,IAAI,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AAlCY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;qCAI0B,8BAAa;GAHvC,qBAAqB,CAkCjC"} \ No newline at end of file diff --git a/backend/dist/users/users.controller.d.ts b/backend/dist/users/users.controller.d.ts new file mode 100644 index 00000000..29cb5663 --- /dev/null +++ b/backend/dist/users/users.controller.d.ts @@ -0,0 +1,107 @@ +import { UsersService } from './users.service'; +declare class SetRoleDto { + role: string; +} +declare class SetPremiumDto { + isPremium: boolean; +} +declare class SetRecipeSharingDto { + canShareRecipes: boolean; +} +declare class AdminCreateUserDto { + username: string; + email: string; + password: string; + role?: string; +} +declare class UpdateEmailDto { + email: string; +} +declare class UpdateProfileDto { + firstName?: string; + lastName?: string; + email?: string; +} +export declare class UsersController { + private readonly usersService; + constructor(usersService: UsersService); + getMe(user: { + userId: number; + username: string; + }): Promise<{ + id: number | undefined; + username: string | undefined; + email: string | undefined; + firstName: string | null | undefined; + lastName: string | null | undefined; + role: string | undefined; + }>; + updateMe(user: { + userId: number; + username: string; + }, dto: UpdateProfileDto): Promise<{ + id: number; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + }>; + listUsers(): import(".prisma/client").Prisma.PrismaPromise<{ + id: number; + createdAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }[]>; + setRole(id: number, caller: { + userId: number; + username: string; + role: string; + }, dto: SetRoleDto): Promise<{ + id: number; + username: string; + role: string; + }>; + setPremium(id: number, dto: SetPremiumDto): Promise<{ + id: number; + username: string; + isPremium: boolean; + }>; + setRecipeSharing(id: number, dto: SetRecipeSharingDto): Promise<{ + id: number; + username: string; + canShareRecipes: boolean; + }>; + adminCreateUser(dto: AdminCreateUserDto): Promise<{ + id: number; + username: string; + email: string; + role: string; + createdAt: Date; + }>; + deleteUser(id: number, caller: { + userId: number; + }): Promise<{ + deleted: boolean; + }>; + resetPassword(id: number, caller: { + userId: number; + }): Promise<{ + to: string; + subject: string; + body: string; + temporaryPassword: string; + }>; + updateEmail(id: number, caller: { + userId: number; + }, dto: UpdateEmailDto): Promise<{ + id: number; + username: string; + email: string; + }>; +} +export {}; diff --git a/backend/dist/users/users.controller.js b/backend/dist/users/users.controller.js new file mode 100644 index 00000000..01ce2804 --- /dev/null +++ b/backend/dist/users/users.controller.js @@ -0,0 +1,252 @@ +"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.UsersController = void 0; +const common_1 = require("@nestjs/common"); +const class_validator_1 = require("class-validator"); +const users_service_1 = require("./users.service"); +const current_user_decorator_1 = require("../auth/decorators/current-user.decorator"); +const roles_decorator_1 = require("../auth/decorators/roles.decorator"); +class SetRoleDto { +} +__decorate([ + (0, class_validator_1.IsIn)(['admin', 'user']), + __metadata("design:type", String) +], SetRoleDto.prototype, "role", void 0); +class SetPremiumDto { +} +__decorate([ + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], SetPremiumDto.prototype, "isPremium", void 0); +class SetRecipeSharingDto { +} +__decorate([ + (0, class_validator_1.IsBoolean)(), + __metadata("design:type", Boolean) +], SetRecipeSharingDto.prototype, "canShareRecipes", void 0); +class AdminCreateUserDto { +} +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(2), + (0, class_validator_1.MaxLength)(50), + __metadata("design:type", String) +], AdminCreateUserDto.prototype, "username", void 0); +__decorate([ + (0, class_validator_1.IsEmail)(), + __metadata("design:type", String) +], AdminCreateUserDto.prototype, "email", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(8), + __metadata("design:type", String) +], AdminCreateUserDto.prototype, "password", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsIn)(['admin', 'user']), + __metadata("design:type", String) +], AdminCreateUserDto.prototype, "role", void 0); +class UpdateEmailDto { +} +__decorate([ + (0, class_validator_1.IsEmail)(), + __metadata("design:type", String) +], UpdateEmailDto.prototype, "email", void 0); +class UpdateProfileDto { +} +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.MaxLength)(100), + __metadata("design:type", String) +], UpdateProfileDto.prototype, "firstName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.MaxLength)(100), + __metadata("design:type", String) +], UpdateProfileDto.prototype, "lastName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsEmail)(), + __metadata("design:type", String) +], UpdateProfileDto.prototype, "email", void 0); +let UsersController = class UsersController { + constructor(usersService) { + this.usersService = usersService; + } + async getMe(user) { + const found = await this.usersService.findById(user.userId); + return { + id: found?.id, + username: found?.username, + email: found?.email, + firstName: found?.firstName, + lastName: found?.lastName, + role: found?.role, + }; + } + async updateMe(user, dto) { + const updated = await this.usersService.updateProfile(user.userId, dto); + return { + id: updated.id, + username: updated.username, + email: updated.email, + firstName: updated.firstName, + lastName: updated.lastName, + }; + } + listUsers() { + return this.usersService.findAll(); + } + async setRole(id, caller, dto) { + if (caller.userId === id) + throw new common_1.BadRequestException('Du kan inte ändra din egen roll'); + const updated = await this.usersService.setRole(id, dto.role); + return { id: updated.id, username: updated.username, role: updated.role }; + } + async setPremium(id, dto) { + const updated = await this.usersService.setPremium(id, dto.isPremium); + return { id: updated.id, username: updated.username, isPremium: updated.isPremium }; + } + async setRecipeSharing(id, dto) { + const updated = await this.usersService.setRecipeSharing(id, dto.canShareRecipes); + return { id: updated.id, username: updated.username, canShareRecipes: updated.canShareRecipes }; + } + async adminCreateUser(dto) { + const user = await this.usersService.adminCreate(dto); + return { id: user.id, username: user.username, email: user.email, role: user.role, createdAt: user.createdAt }; + } + async deleteUser(id, caller) { + if (caller.userId === id) + throw new common_1.BadRequestException('Du kan inte ta bort ditt eget konto'); + await this.usersService.deleteUser(id); + return { deleted: true }; + } + async resetPassword(id, caller) { + if (caller.userId === id) + throw new common_1.BadRequestException('Du kan inte återställa ditt eget lösenord härifrån'); + const user = await this.usersService.findById(id); + if (!user) + throw new common_1.BadRequestException('Användaren hittades inte'); + const { temporaryPassword } = await this.usersService.resetPassword(id); + const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'appen'; + const displayName = user.firstName ? user.firstName : user.username; + return { + to: user.email, + subject: 'Ditt lösenord har återställts', + body: `Hej ${displayName},\n\nDitt lösenord har återställts av en administratör.\nDitt nya tillôlliga lösenord är: ${temporaryPassword}\n\nLogga in på ${appUrl} och byt lösenord snarast.\n\nHälsningar`, + temporaryPassword, + }; + } + async updateEmail(id, caller, dto) { + if (caller.userId === id) + throw new common_1.BadRequestException('Använd "Min profil" för att ändra din egen e-post'); + const updated = await this.usersService.updateEmail(id, dto.email); + return { id: updated.id, username: updated.username, email: updated.email }; + } +}; +exports.UsersController = UsersController; +__decorate([ + (0, common_1.Get)('me'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "getMe", null); +__decorate([ + (0, common_1.Patch)('me'), + __param(0, (0, current_user_decorator_1.CurrentUser)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, UpdateProfileDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "updateMe", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Get)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], UsersController.prototype, "listUsers", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Patch)(':id/role'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object, SetRoleDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "setRole", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Patch)(':id/premium'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, SetPremiumDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "setPremium", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Patch)(':id/recipe-sharing'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, SetRecipeSharingDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "setRecipeSharing", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [AdminCreateUserDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "adminCreateUser", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Delete)(':id'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "deleteUser", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Post)(':id/reset-password'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "resetPassword", null); +__decorate([ + (0, roles_decorator_1.Roles)('admin'), + (0, common_1.Patch)(':id/email'), + __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)), + __param(1, (0, current_user_decorator_1.CurrentUser)()), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Number, Object, UpdateEmailDto]), + __metadata("design:returntype", Promise) +], UsersController.prototype, "updateEmail", null); +exports.UsersController = UsersController = __decorate([ + (0, common_1.Controller)('users'), + __metadata("design:paramtypes", [users_service_1.UsersService]) +], UsersController); +//# sourceMappingURL=users.controller.js.map \ No newline at end of file diff --git a/backend/dist/users/users.controller.js.map b/backend/dist/users/users.controller.js.map new file mode 100644 index 00000000..18305453 --- /dev/null +++ b/backend/dist/users/users.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/users/users.module.d.ts b/backend/dist/users/users.module.d.ts new file mode 100644 index 00000000..d1340f41 --- /dev/null +++ b/backend/dist/users/users.module.d.ts @@ -0,0 +1,2 @@ +export declare class UsersModule { +} diff --git a/backend/dist/users/users.module.js b/backend/dist/users/users.module.js new file mode 100644 index 00000000..b861ce85 --- /dev/null +++ b/backend/dist/users/users.module.js @@ -0,0 +1,26 @@ +"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.UsersModule = void 0; +const common_1 = require("@nestjs/common"); +const users_service_1 = require("./users.service"); +const users_controller_1 = require("./users.controller"); +const admin_bootstrap_service_1 = require("./admin-bootstrap.service"); +const prisma_module_1 = require("../prisma/prisma.module"); +let UsersModule = class UsersModule { +}; +exports.UsersModule = UsersModule; +exports.UsersModule = UsersModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + providers: [users_service_1.UsersService, admin_bootstrap_service_1.AdminBootstrapService], + controllers: [users_controller_1.UsersController], + exports: [users_service_1.UsersService], + }) +], UsersModule); +//# sourceMappingURL=users.module.js.map \ No newline at end of file diff --git a/backend/dist/users/users.module.js.map b/backend/dist/users/users.module.js.map new file mode 100644 index 00000000..761ef2a2 --- /dev/null +++ b/backend/dist/users/users.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.module.js","sourceRoot":"","sources":["../../src/users/users.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,mDAA+C;AAC/C,yDAAqD;AACrD,uEAAkE;AAClE,2DAAuD;AAQhD,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IANvB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,SAAS,EAAE,CAAC,4BAAY,EAAE,+CAAqB,CAAC;QAChD,WAAW,EAAE,CAAC,kCAAe,CAAC;QAC9B,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,WAAW,CAAG"} \ No newline at end of file diff --git a/backend/dist/users/users.service.d.ts b/backend/dist/users/users.service.d.ts new file mode 100644 index 00000000..a54a0e3f --- /dev/null +++ b/backend/dist/users/users.service.d.ts @@ -0,0 +1,162 @@ +import { PrismaService } from '../prisma/prisma.service'; +export declare class UsersService { + private readonly prisma; + constructor(prisma: PrismaService); + findByUsername(username: string): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + } | null, null, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + findById(id: number): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + } | null, null, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + create(data: { + username: string; + email: string; + passwordHash: string; + }): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + updateProfile(id: number, data: { + firstName?: string; + lastName?: string; + email?: string; + }): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + findAll(): import(".prisma/client").Prisma.PrismaPromise<{ + id: number; + createdAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }[]>; + setRole(id: number, role: string): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + setPremium(id: number, isPremium: boolean): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + setRecipeSharing(id: number, canShareRecipes: boolean): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + adminCreate(data: { + username: string; + email: string; + password: string; + role?: string; + }): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }>; + deleteUser(id: number): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; + resetPassword(id: number): Promise<{ + temporaryPassword: string; + }>; + updateEmail(id: number, email: string): import(".prisma/client").Prisma.Prisma__UserClient<{ + id: number; + createdAt: Date; + updatedAt: Date; + username: string; + email: string; + firstName: string | null; + lastName: string | null; + passwordHash: string; + role: string; + isPremium: boolean; + canShareRecipes: boolean; + }, never, import("@prisma/client/runtime/library").DefaultArgs, import(".prisma/client").Prisma.PrismaClientOptions>; +} diff --git a/backend/dist/users/users.service.js b/backend/dist/users/users.service.js new file mode 100644 index 00000000..233f4f63 --- /dev/null +++ b/backend/dist/users/users.service.js @@ -0,0 +1,88 @@ +"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.UsersService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../prisma/prisma.service"); +const bcrypt = require("bcryptjs"); +const crypto = require("crypto"); +let UsersService = class UsersService { + constructor(prisma) { + this.prisma = prisma; + } + findByUsername(username) { + return this.prisma.user.findUnique({ where: { username } }); + } + findById(id) { + return this.prisma.user.findUnique({ where: { id } }); + } + create(data) { + return this.prisma.user.create({ data }); + } + updateProfile(id, data) { + return this.prisma.user.update({ where: { id }, data }); + } + findAll() { + return this.prisma.user.findMany({ + select: { + id: true, + username: true, + email: true, + firstName: true, + lastName: true, + role: true, + isPremium: true, + canShareRecipes: true, + createdAt: true, + }, + orderBy: { username: 'asc' }, + }); + } + setRole(id, role) { + return this.prisma.user.update({ where: { id }, data: { role } }); + } + setPremium(id, isPremium) { + return this.prisma.user.update({ where: { id }, data: { isPremium } }); + } + setRecipeSharing(id, canShareRecipes) { + return this.prisma.user.update({ where: { id }, data: { canShareRecipes } }); + } + async adminCreate(data) { + const existing = await this.prisma.user.findFirst({ + where: { OR: [{ username: data.username }, { email: data.email }] }, + }); + if (existing) { + throw new common_1.ConflictException(existing.username === data.username ? 'Användarnamnet är redan taget' : 'E-postadressen används redan'); + } + const passwordHash = await bcrypt.hash(data.password, 12); + return this.prisma.user.create({ + data: { username: data.username, email: data.email, passwordHash, role: data.role ?? 'user' }, + }); + } + deleteUser(id) { + return this.prisma.user.delete({ where: { id } }); + } + async resetPassword(id) { + const temporaryPassword = crypto.randomBytes(9).toString('base64url').slice(0, 12); + const passwordHash = await bcrypt.hash(temporaryPassword, 12); + await this.prisma.user.update({ where: { id }, data: { passwordHash } }); + return { temporaryPassword }; + } + updateEmail(id, email) { + return this.prisma.user.update({ where: { id }, data: { email } }); + } +}; +exports.UsersService = UsersService; +exports.UsersService = UsersService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], UsersService); +//# sourceMappingURL=users.service.js.map \ No newline at end of file diff --git a/backend/dist/users/users.service.js.map b/backend/dist/users/users.service.js.map new file mode 100644 index 00000000..8c75a65f --- /dev/null +++ b/backend/dist/users/users.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.service.js","sourceRoot":"","sources":["../../src/users/users.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/src/meal-plan/meal-plan.service.ts b/backend/src/meal-plan/meal-plan.service.ts index a8ed5a6c..d0ab84a6 100644 --- a/backend/src/meal-plan/meal-plan.service.ts +++ b/backend/src/meal-plan/meal-plan.service.ts @@ -76,8 +76,11 @@ export class MealPlanService { const entryServings = (entry as any).servings as number | null; const scale = recipeServings && entryServings ? entryServings / recipeServings : 1; for (const ing of entry.recipe.ingredients) { + if (!ing.product || !ing.unit) { + continue; + } const key = `${ing.product.id}-${ing.unit}`; - const qty = Number(ing.quantity) * scale; + const qty = Number(ing.quantity ?? 0) * scale; const existing = map.get(key); if (existing) { existing.quantity += qty; diff --git a/backend/src/recipes/recipes.service.ts b/backend/src/recipes/recipes.service.ts index 458aca4e..9d205191 100644 --- a/backend/src/recipes/recipes.service.ts +++ b/backend/src/recipes/recipes.service.ts @@ -131,6 +131,9 @@ export class RecipesService { }; } + const requiredUnit = (ingredient.unit ?? '').trim(); + const requiredQuantity = Number(ingredient.quantity ?? 0); + // Täcks ingrediensen av pantry (inkl. alternativ)? const coveredByPantry = pantryProductIds.has(ingredient.productId) || @@ -144,11 +147,11 @@ export class RecipesService { ingredientId: ingredient.id, productId: ingredient.productId, productName: ingredient.product.canonicalName || ingredient.product.name, - requiredQuantity: Number(ingredient.quantity), - requiredUnit: ingredient.unit, + requiredQuantity, + requiredUnit, note: ingredient.note, - availableQuantity: Number(ingredient.quantity), - availableUnit: ingredient.unit, + availableQuantity: requiredQuantity, + availableUnit: requiredUnit, matchingInventoryItems: [], otherInventoryItems: [], status: 'enough' as const, @@ -173,7 +176,10 @@ export class RecipesService { // Hitta inventory-poster med samma enhet const sameUnitItems = inventoryItems.filter( - (item: any) => item.unit.trim().toLowerCase() === ingredient.unit.trim().toLowerCase(), + (item: any) => + requiredUnit + ? item.unit.trim().toLowerCase() === requiredUnit.toLowerCase() + : true, ); const availableSameUnit = sameUnitItems.reduce( (sum: number, item: any) => sum + Number(item.quantity), @@ -182,7 +188,10 @@ export class RecipesService { // Hitta inventory-poster med annan enhet och konvertera (endast viktbaserade enheter) const otherUnitItems = inventoryItems.filter( - (item: any) => item.unit.trim().toLowerCase() !== ingredient.unit.trim().toLowerCase(), + (item: any) => + requiredUnit + ? item.unit.trim().toLowerCase() !== requiredUnit.toLowerCase() + : false, ); let availableOtherUnit = 0; @@ -192,7 +201,7 @@ export class RecipesService { const convertedQuantity = convertUnit( Number(item.quantity), item.unit, - ingredient.unit, + requiredUnit, ); availableOtherUnit += convertedQuantity; } catch { @@ -204,7 +213,7 @@ export class RecipesService { const totalAvailable = availableSameUnit + availableOtherUnit; let status: 'enough' | 'missing' | 'unit_mismatch'; - if (totalAvailable >= Number(ingredient.quantity)) { + if (totalAvailable >= requiredQuantity) { status = 'enough'; } else if (availableSameUnit === 0 && availableOtherUnit > 0) { status = 'unit_mismatch'; @@ -216,11 +225,11 @@ export class RecipesService { ingredientId: ingredient.id, productId: ingredient.productId, productName: ingredient.product.canonicalName || ingredient.product.name, - requiredQuantity: Number(ingredient.quantity), - requiredUnit: ingredient.unit, + requiredQuantity, + requiredUnit, note: ingredient.note, availableQuantity: totalAvailable, - availableUnit: ingredient.unit, + availableUnit: requiredUnit, matchingInventoryItems: sameUnitItems.map((item: any) => ({ id: item.id, quantity: item.quantity, @@ -231,11 +240,11 @@ export class RecipesService { })), otherInventoryItems: otherUnitItems.map((item: any) => { // Kolla om konvertering är möjlig (samma enhetskategori) - const canConvertUnits = canConvert(item.unit, ingredient.unit); + const canConvertUnits = requiredUnit ? canConvert(item.unit, requiredUnit) : false; let convertedQuantity = 0; if (canConvertUnits) { try { - convertedQuantity = convertUnit(Number(item.quantity), item.unit, ingredient.unit); + convertedQuantity = convertUnit(Number(item.quantity), item.unit, requiredUnit); } catch { convertedQuantity = 0; } @@ -252,7 +261,7 @@ export class RecipesService { }), status, fromPantry: false, - missingQuantity: status === 'missing' ? Math.max(0, Number(ingredient.quantity) - totalAvailable) : 0, + missingQuantity: status === 'missing' ? Math.max(0, requiredQuantity - totalAvailable) : 0, }; }), ); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 0a4fa0ef..872e1b6b 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -19,5 +19,5 @@ "noFallthroughCasesInSwitch": false, "types": ["node", "jest"] }, - "exclude": ["node_modules", "prisma.config.ts"] + "exclude": ["node_modules", "dist", "prisma.config.ts"] } diff --git a/backend/tsconfig.tsbuildinfo b/backend/tsconfig.tsbuildinfo new file mode 100644 index 00000000..c7c05302 --- /dev/null +++ b/backend/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.dom.asynciterable.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/typescript/lib/lib.es2021.full.d.ts","./node_modules/reflect-metadata/index.d.ts","./node_modules/@nestjs/common/decorators/core/bind.decorator.d.ts","./node_modules/@nestjs/common/interfaces/abstract.interface.d.ts","./node_modules/@nestjs/common/interfaces/controllers/controller-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/controllers/controller.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/arguments-host.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/exception-filter.interface.d.ts","./node_modules/rxjs/dist/types/internal/subscription.d.ts","./node_modules/rxjs/dist/types/internal/subscriber.d.ts","./node_modules/rxjs/dist/types/internal/operator.d.ts","./node_modules/rxjs/dist/types/internal/observable.d.ts","./node_modules/rxjs/dist/types/internal/types.d.ts","./node_modules/rxjs/dist/types/internal/operators/audit.d.ts","./node_modules/rxjs/dist/types/internal/operators/audittime.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffer.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffercount.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffertime.d.ts","./node_modules/rxjs/dist/types/internal/operators/buffertoggle.d.ts","./node_modules/rxjs/dist/types/internal/operators/bufferwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/catcherror.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatestall.d.ts","./node_modules/rxjs/dist/types/internal/operators/combineall.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatest.d.ts","./node_modules/rxjs/dist/types/internal/operators/combinelatestwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/concat.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatall.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatmapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/concatwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/connect.d.ts","./node_modules/rxjs/dist/types/internal/operators/count.d.ts","./node_modules/rxjs/dist/types/internal/operators/debounce.d.ts","./node_modules/rxjs/dist/types/internal/operators/debouncetime.d.ts","./node_modules/rxjs/dist/types/internal/operators/defaultifempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/delay.d.ts","./node_modules/rxjs/dist/types/internal/operators/delaywhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/dematerialize.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinct.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinctuntilchanged.d.ts","./node_modules/rxjs/dist/types/internal/operators/distinctuntilkeychanged.d.ts","./node_modules/rxjs/dist/types/internal/operators/elementat.d.ts","./node_modules/rxjs/dist/types/internal/operators/endwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/every.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaustall.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaust.d.ts","./node_modules/rxjs/dist/types/internal/operators/exhaustmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/expand.d.ts","./node_modules/rxjs/dist/types/internal/operators/filter.d.ts","./node_modules/rxjs/dist/types/internal/operators/finalize.d.ts","./node_modules/rxjs/dist/types/internal/operators/find.d.ts","./node_modules/rxjs/dist/types/internal/operators/findindex.d.ts","./node_modules/rxjs/dist/types/internal/operators/first.d.ts","./node_modules/rxjs/dist/types/internal/subject.d.ts","./node_modules/rxjs/dist/types/internal/operators/groupby.d.ts","./node_modules/rxjs/dist/types/internal/operators/ignoreelements.d.ts","./node_modules/rxjs/dist/types/internal/operators/isempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/last.d.ts","./node_modules/rxjs/dist/types/internal/operators/map.d.ts","./node_modules/rxjs/dist/types/internal/operators/mapto.d.ts","./node_modules/rxjs/dist/types/internal/notification.d.ts","./node_modules/rxjs/dist/types/internal/operators/materialize.d.ts","./node_modules/rxjs/dist/types/internal/operators/max.d.ts","./node_modules/rxjs/dist/types/internal/operators/merge.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergeall.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergemap.d.ts","./node_modules/rxjs/dist/types/internal/operators/flatmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergemapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergescan.d.ts","./node_modules/rxjs/dist/types/internal/operators/mergewith.d.ts","./node_modules/rxjs/dist/types/internal/operators/min.d.ts","./node_modules/rxjs/dist/types/internal/observable/connectableobservable.d.ts","./node_modules/rxjs/dist/types/internal/operators/multicast.d.ts","./node_modules/rxjs/dist/types/internal/operators/observeon.d.ts","./node_modules/rxjs/dist/types/internal/operators/onerrorresumenextwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/pairwise.d.ts","./node_modules/rxjs/dist/types/internal/operators/partition.d.ts","./node_modules/rxjs/dist/types/internal/operators/pluck.d.ts","./node_modules/rxjs/dist/types/internal/operators/publish.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishbehavior.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishlast.d.ts","./node_modules/rxjs/dist/types/internal/operators/publishreplay.d.ts","./node_modules/rxjs/dist/types/internal/operators/race.d.ts","./node_modules/rxjs/dist/types/internal/operators/racewith.d.ts","./node_modules/rxjs/dist/types/internal/operators/reduce.d.ts","./node_modules/rxjs/dist/types/internal/operators/repeat.d.ts","./node_modules/rxjs/dist/types/internal/operators/repeatwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/retry.d.ts","./node_modules/rxjs/dist/types/internal/operators/retrywhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/refcount.d.ts","./node_modules/rxjs/dist/types/internal/operators/sample.d.ts","./node_modules/rxjs/dist/types/internal/operators/sampletime.d.ts","./node_modules/rxjs/dist/types/internal/operators/scan.d.ts","./node_modules/rxjs/dist/types/internal/operators/sequenceequal.d.ts","./node_modules/rxjs/dist/types/internal/operators/share.d.ts","./node_modules/rxjs/dist/types/internal/operators/sharereplay.d.ts","./node_modules/rxjs/dist/types/internal/operators/single.d.ts","./node_modules/rxjs/dist/types/internal/operators/skip.d.ts","./node_modules/rxjs/dist/types/internal/operators/skiplast.d.ts","./node_modules/rxjs/dist/types/internal/operators/skipuntil.d.ts","./node_modules/rxjs/dist/types/internal/operators/skipwhile.d.ts","./node_modules/rxjs/dist/types/internal/operators/startwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/subscribeon.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchall.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchmap.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchmapto.d.ts","./node_modules/rxjs/dist/types/internal/operators/switchscan.d.ts","./node_modules/rxjs/dist/types/internal/operators/take.d.ts","./node_modules/rxjs/dist/types/internal/operators/takelast.d.ts","./node_modules/rxjs/dist/types/internal/operators/takeuntil.d.ts","./node_modules/rxjs/dist/types/internal/operators/takewhile.d.ts","./node_modules/rxjs/dist/types/internal/operators/tap.d.ts","./node_modules/rxjs/dist/types/internal/operators/throttle.d.ts","./node_modules/rxjs/dist/types/internal/operators/throttletime.d.ts","./node_modules/rxjs/dist/types/internal/operators/throwifempty.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeinterval.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeout.d.ts","./node_modules/rxjs/dist/types/internal/operators/timeoutwith.d.ts","./node_modules/rxjs/dist/types/internal/operators/timestamp.d.ts","./node_modules/rxjs/dist/types/internal/operators/toarray.d.ts","./node_modules/rxjs/dist/types/internal/operators/window.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowcount.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowtime.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowtoggle.d.ts","./node_modules/rxjs/dist/types/internal/operators/windowwhen.d.ts","./node_modules/rxjs/dist/types/internal/operators/withlatestfrom.d.ts","./node_modules/rxjs/dist/types/internal/operators/zip.d.ts","./node_modules/rxjs/dist/types/internal/operators/zipall.d.ts","./node_modules/rxjs/dist/types/internal/operators/zipwith.d.ts","./node_modules/rxjs/dist/types/operators/index.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/action.d.ts","./node_modules/rxjs/dist/types/internal/scheduler.d.ts","./node_modules/rxjs/dist/types/internal/testing/testmessage.d.ts","./node_modules/rxjs/dist/types/internal/testing/subscriptionlog.d.ts","./node_modules/rxjs/dist/types/internal/testing/subscriptionloggable.d.ts","./node_modules/rxjs/dist/types/internal/testing/coldobservable.d.ts","./node_modules/rxjs/dist/types/internal/testing/hotobservable.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asyncscheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/timerhandle.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asyncaction.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/virtualtimescheduler.d.ts","./node_modules/rxjs/dist/types/internal/testing/testscheduler.d.ts","./node_modules/rxjs/dist/types/testing/index.d.ts","./node_modules/rxjs/dist/types/internal/symbol/observable.d.ts","./node_modules/rxjs/dist/types/internal/observable/dom/animationframes.d.ts","./node_modules/rxjs/dist/types/internal/behaviorsubject.d.ts","./node_modules/rxjs/dist/types/internal/replaysubject.d.ts","./node_modules/rxjs/dist/types/internal/asyncsubject.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asapscheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/asap.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/async.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/queuescheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/queue.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/animationframescheduler.d.ts","./node_modules/rxjs/dist/types/internal/scheduler/animationframe.d.ts","./node_modules/rxjs/dist/types/internal/util/identity.d.ts","./node_modules/rxjs/dist/types/internal/util/pipe.d.ts","./node_modules/rxjs/dist/types/internal/util/noop.d.ts","./node_modules/rxjs/dist/types/internal/util/isobservable.d.ts","./node_modules/rxjs/dist/types/internal/lastvaluefrom.d.ts","./node_modules/rxjs/dist/types/internal/firstvaluefrom.d.ts","./node_modules/rxjs/dist/types/internal/util/argumentoutofrangeerror.d.ts","./node_modules/rxjs/dist/types/internal/util/emptyerror.d.ts","./node_modules/rxjs/dist/types/internal/util/notfounderror.d.ts","./node_modules/rxjs/dist/types/internal/util/objectunsubscribederror.d.ts","./node_modules/rxjs/dist/types/internal/util/sequenceerror.d.ts","./node_modules/rxjs/dist/types/internal/util/unsubscriptionerror.d.ts","./node_modules/rxjs/dist/types/internal/observable/bindcallback.d.ts","./node_modules/rxjs/dist/types/internal/observable/bindnodecallback.d.ts","./node_modules/rxjs/dist/types/internal/anycatcher.d.ts","./node_modules/rxjs/dist/types/internal/observable/combinelatest.d.ts","./node_modules/rxjs/dist/types/internal/observable/concat.d.ts","./node_modules/rxjs/dist/types/internal/observable/connectable.d.ts","./node_modules/rxjs/dist/types/internal/observable/defer.d.ts","./node_modules/rxjs/dist/types/internal/observable/empty.d.ts","./node_modules/rxjs/dist/types/internal/observable/forkjoin.d.ts","./node_modules/rxjs/dist/types/internal/observable/from.d.ts","./node_modules/rxjs/dist/types/internal/observable/fromevent.d.ts","./node_modules/rxjs/dist/types/internal/observable/fromeventpattern.d.ts","./node_modules/rxjs/dist/types/internal/observable/generate.d.ts","./node_modules/rxjs/dist/types/internal/observable/iif.d.ts","./node_modules/rxjs/dist/types/internal/observable/interval.d.ts","./node_modules/rxjs/dist/types/internal/observable/merge.d.ts","./node_modules/rxjs/dist/types/internal/observable/never.d.ts","./node_modules/rxjs/dist/types/internal/observable/of.d.ts","./node_modules/rxjs/dist/types/internal/observable/onerrorresumenext.d.ts","./node_modules/rxjs/dist/types/internal/observable/pairs.d.ts","./node_modules/rxjs/dist/types/internal/observable/partition.d.ts","./node_modules/rxjs/dist/types/internal/observable/race.d.ts","./node_modules/rxjs/dist/types/internal/observable/range.d.ts","./node_modules/rxjs/dist/types/internal/observable/throwerror.d.ts","./node_modules/rxjs/dist/types/internal/observable/timer.d.ts","./node_modules/rxjs/dist/types/internal/observable/using.d.ts","./node_modules/rxjs/dist/types/internal/observable/zip.d.ts","./node_modules/rxjs/dist/types/internal/scheduled/scheduled.d.ts","./node_modules/rxjs/dist/types/internal/config.d.ts","./node_modules/rxjs/dist/types/index.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/ws-exception-filter.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validation-error.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/execution-context.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/can-activate.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/custom-route-param-factory.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/nest-interceptor.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/paramtype.interface.d.ts","./node_modules/@nestjs/common/interfaces/type.interface.d.ts","./node_modules/@nestjs/common/interfaces/features/pipe-transform.interface.d.ts","./node_modules/@nestjs/common/enums/request-method.enum.d.ts","./node_modules/@nestjs/common/enums/http-status.enum.d.ts","./node_modules/@nestjs/common/enums/shutdown-signal.enum.d.ts","./node_modules/@nestjs/common/enums/version-type.enum.d.ts","./node_modules/@nestjs/common/enums/index.d.ts","./node_modules/@nestjs/common/interfaces/version-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-configuration.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-consumer.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/middleware-config-proxy.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/nest-middleware.interface.d.ts","./node_modules/@nestjs/common/interfaces/middleware/index.d.ts","./node_modules/@nestjs/common/interfaces/global-prefix-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/before-application-shutdown.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-application-bootstrap.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-application-shutdown.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-destroy.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/on-init.interface.d.ts","./node_modules/@nestjs/common/interfaces/hooks/index.d.ts","./node_modules/@nestjs/common/interfaces/http/http-exception-body.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/http-redirect-response.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/cors-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/https-options.interface.d.ts","./node_modules/@nestjs/common/services/logger.service.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-context-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/http-server.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/message-event.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/raw-body-request.interface.d.ts","./node_modules/@nestjs/common/interfaces/http/index.d.ts","./node_modules/@nestjs/common/interfaces/injectable.interface.d.ts","./node_modules/@nestjs/common/interfaces/microservices/nest-hybrid-application-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/forward-reference.interface.d.ts","./node_modules/@nestjs/common/interfaces/scope-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/injection-token.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/optional-factory-dependency.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/provider.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/module-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/dynamic-module.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/introspection-result.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/nest-module.interface.d.ts","./node_modules/@nestjs/common/interfaces/modules/index.d.ts","./node_modules/@nestjs/common/interfaces/nest-application-context.interface.d.ts","./node_modules/@nestjs/common/interfaces/websockets/web-socket-adapter.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-application.interface.d.ts","./node_modules/@nestjs/common/interfaces/nest-microservice.interface.d.ts","./node_modules/@nestjs/common/interfaces/index.d.ts","./node_modules/@nestjs/common/decorators/core/catch.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/controller.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/dependencies.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/exception-filters.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/inject.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/injectable.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/optional.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/set-metadata.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-guards.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-interceptors.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/use-pipes.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/apply-decorators.d.ts","./node_modules/@nestjs/common/decorators/core/version.decorator.d.ts","./node_modules/@nestjs/common/decorators/core/index.d.ts","./node_modules/@nestjs/common/decorators/modules/global.decorator.d.ts","./node_modules/@nestjs/common/decorators/modules/module.decorator.d.ts","./node_modules/@nestjs/common/decorators/modules/index.d.ts","./node_modules/@nestjs/common/decorators/http/request-mapping.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/route-params.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/http-code.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/create-route-param-metadata.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/render.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/header.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/redirect.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/sse.decorator.d.ts","./node_modules/@nestjs/common/decorators/http/index.d.ts","./node_modules/@nestjs/common/decorators/index.d.ts","./node_modules/@nestjs/common/exceptions/http.exception.d.ts","./node_modules/@nestjs/common/exceptions/bad-request.exception.d.ts","./node_modules/@nestjs/common/exceptions/unauthorized.exception.d.ts","./node_modules/@nestjs/common/exceptions/method-not-allowed.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-found.exception.d.ts","./node_modules/@nestjs/common/exceptions/forbidden.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-acceptable.exception.d.ts","./node_modules/@nestjs/common/exceptions/request-timeout.exception.d.ts","./node_modules/@nestjs/common/exceptions/conflict.exception.d.ts","./node_modules/@nestjs/common/exceptions/gone.exception.d.ts","./node_modules/@nestjs/common/exceptions/payload-too-large.exception.d.ts","./node_modules/@nestjs/common/exceptions/unsupported-media-type.exception.d.ts","./node_modules/@nestjs/common/exceptions/unprocessable-entity.exception.d.ts","./node_modules/@nestjs/common/exceptions/internal-server-error.exception.d.ts","./node_modules/@nestjs/common/exceptions/not-implemented.exception.d.ts","./node_modules/@nestjs/common/exceptions/http-version-not-supported.exception.d.ts","./node_modules/@nestjs/common/exceptions/bad-gateway.exception.d.ts","./node_modules/@nestjs/common/exceptions/service-unavailable.exception.d.ts","./node_modules/@nestjs/common/exceptions/gateway-timeout.exception.d.ts","./node_modules/@nestjs/common/exceptions/im-a-teapot.exception.d.ts","./node_modules/@nestjs/common/exceptions/precondition-failed.exception.d.ts","./node_modules/@nestjs/common/exceptions/misdirected.exception.d.ts","./node_modules/@nestjs/common/exceptions/index.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/streamable-options.interface.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/streamable-handler-response.interface.d.ts","./node_modules/@nestjs/common/file-stream/interfaces/index.d.ts","./node_modules/@nestjs/common/services/console-logger.service.d.ts","./node_modules/@nestjs/common/services/index.d.ts","./node_modules/@nestjs/common/file-stream/streamable-file.d.ts","./node_modules/@nestjs/common/file-stream/index.d.ts","./node_modules/@nestjs/common/module-utils/constants.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-async-options.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-cls.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/configurable-module-host.interface.d.ts","./node_modules/@nestjs/common/module-utils/interfaces/index.d.ts","./node_modules/@nestjs/common/module-utils/configurable-module.builder.d.ts","./node_modules/@nestjs/common/module-utils/index.d.ts","./node_modules/@nestjs/common/pipes/default-value.pipe.d.ts","./node_modules/@nestjs/common/interfaces/external/class-transform-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/transformer-package.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validator-options.interface.d.ts","./node_modules/@nestjs/common/interfaces/external/validator-package.interface.d.ts","./node_modules/@nestjs/common/utils/http-error-by-code.util.d.ts","./node_modules/@nestjs/common/pipes/validation.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-array.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-bool.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-int.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-float.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-enum.pipe.d.ts","./node_modules/@nestjs/common/pipes/parse-uuid.pipe.d.ts","./node_modules/@nestjs/common/pipes/file/interfaces/file.interface.d.ts","./node_modules/@nestjs/common/pipes/file/interfaces/index.d.ts","./node_modules/@nestjs/common/pipes/file/file-validator.interface.d.ts","./node_modules/@nestjs/common/pipes/file/file-type.validator.d.ts","./node_modules/@nestjs/common/pipes/file/max-file-size.validator.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file-options.interface.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file.pipe.d.ts","./node_modules/@nestjs/common/pipes/file/parse-file-pipe.builder.d.ts","./node_modules/@nestjs/common/pipes/file/index.d.ts","./node_modules/@nestjs/common/pipes/index.d.ts","./node_modules/@nestjs/common/serializer/class-serializer.interfaces.d.ts","./node_modules/@nestjs/common/serializer/class-serializer.interceptor.d.ts","./node_modules/@nestjs/common/serializer/decorators/serialize-options.decorator.d.ts","./node_modules/@nestjs/common/serializer/decorators/index.d.ts","./node_modules/@nestjs/common/serializer/index.d.ts","./node_modules/@nestjs/common/utils/forward-ref.util.d.ts","./node_modules/@nestjs/common/utils/index.d.ts","./node_modules/@nestjs/common/index.d.ts","./node_modules/@nestjs/core/adapters/http-adapter.d.ts","./node_modules/@nestjs/core/adapters/index.d.ts","./node_modules/@nestjs/common/constants.d.ts","./node_modules/@nestjs/core/inspector/interfaces/edge.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/entrypoint.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/extras.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/node.interface.d.ts","./node_modules/@nestjs/core/injector/settlement-signal.d.ts","./node_modules/@nestjs/core/injector/injector.d.ts","./node_modules/@nestjs/core/inspector/interfaces/serialized-graph-metadata.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/serialized-graph-json.interface.d.ts","./node_modules/@nestjs/core/inspector/serialized-graph.d.ts","./node_modules/@nestjs/core/injector/module-token-factory.d.ts","./node_modules/@nestjs/core/injector/compiler.d.ts","./node_modules/@nestjs/core/injector/modules-container.d.ts","./node_modules/@nestjs/core/injector/container.d.ts","./node_modules/@nestjs/core/injector/instance-links-host.d.ts","./node_modules/@nestjs/core/injector/abstract-instance-resolver.d.ts","./node_modules/@nestjs/core/injector/module-ref.d.ts","./node_modules/@nestjs/core/injector/module.d.ts","./node_modules/@nestjs/core/injector/instance-wrapper.d.ts","./node_modules/@nestjs/core/router/interfaces/exclude-route-metadata.interface.d.ts","./node_modules/@nestjs/core/application-config.d.ts","./node_modules/@nestjs/core/constants.d.ts","./node_modules/@nestjs/core/discovery/discovery-module.d.ts","./node_modules/@nestjs/core/discovery/discovery-service.d.ts","./node_modules/@nestjs/core/discovery/index.d.ts","./node_modules/@nestjs/core/helpers/http-adapter-host.d.ts","./node_modules/@nestjs/core/exceptions/base-exception-filter.d.ts","./node_modules/@nestjs/core/exceptions/index.d.ts","./node_modules/@nestjs/core/helpers/context-id-factory.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/exception-filter-metadata.interface.d.ts","./node_modules/@nestjs/core/exceptions/exceptions-handler.d.ts","./node_modules/@nestjs/core/router/router-proxy.d.ts","./node_modules/@nestjs/core/helpers/context-creator.d.ts","./node_modules/@nestjs/core/exceptions/base-exception-filter-context.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter-metadata.interface.d.ts","./node_modules/@nestjs/common/interfaces/exceptions/index.d.ts","./node_modules/@nestjs/core/exceptions/external-exception-filter.d.ts","./node_modules/@nestjs/core/exceptions/external-exceptions-handler.d.ts","./node_modules/@nestjs/core/exceptions/external-exception-filter-context.d.ts","./node_modules/@nestjs/core/guards/constants.d.ts","./node_modules/@nestjs/core/helpers/execution-context-host.d.ts","./node_modules/@nestjs/core/guards/guards-consumer.d.ts","./node_modules/@nestjs/core/guards/guards-context-creator.d.ts","./node_modules/@nestjs/core/guards/index.d.ts","./node_modules/@nestjs/core/interceptors/interceptors-consumer.d.ts","./node_modules/@nestjs/core/interceptors/interceptors-context-creator.d.ts","./node_modules/@nestjs/core/interceptors/index.d.ts","./node_modules/@nestjs/common/enums/route-paramtypes.enum.d.ts","./node_modules/@nestjs/core/pipes/params-token-factory.d.ts","./node_modules/@nestjs/core/pipes/pipes-consumer.d.ts","./node_modules/@nestjs/core/pipes/pipes-context-creator.d.ts","./node_modules/@nestjs/core/pipes/index.d.ts","./node_modules/@nestjs/core/helpers/context-utils.d.ts","./node_modules/@nestjs/core/injector/inquirer/inquirer-constants.d.ts","./node_modules/@nestjs/core/injector/inquirer/index.d.ts","./node_modules/@nestjs/core/interfaces/module-definition.interface.d.ts","./node_modules/@nestjs/core/interfaces/module-override.interface.d.ts","./node_modules/@nestjs/core/inspector/interfaces/enhancer-metadata-cache-entry.interface.d.ts","./node_modules/@nestjs/core/inspector/graph-inspector.d.ts","./node_modules/@nestjs/core/metadata-scanner.d.ts","./node_modules/@nestjs/core/scanner.d.ts","./node_modules/@nestjs/core/injector/instance-loader.d.ts","./node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader-options.interface.d.ts","./node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader.d.ts","./node_modules/@nestjs/core/injector/index.d.ts","./node_modules/@nestjs/core/helpers/interfaces/external-handler-metadata.interface.d.ts","./node_modules/@nestjs/core/helpers/interfaces/params-metadata.interface.d.ts","./node_modules/@nestjs/core/helpers/external-context-creator.d.ts","./node_modules/@nestjs/core/helpers/index.d.ts","./node_modules/@nestjs/core/inspector/initialize-on-preview.allowlist.d.ts","./node_modules/@nestjs/core/inspector/partial-graph.host.d.ts","./node_modules/@nestjs/core/inspector/index.d.ts","./node_modules/@nestjs/core/middleware/route-info-path-extractor.d.ts","./node_modules/@nestjs/core/middleware/routes-mapper.d.ts","./node_modules/@nestjs/core/middleware/builder.d.ts","./node_modules/@nestjs/core/middleware/index.d.ts","./node_modules/@nestjs/core/nest-application-context.d.ts","./node_modules/@nestjs/core/nest-application.d.ts","./node_modules/@nestjs/common/interfaces/microservices/nest-microservice-options.interface.d.ts","./node_modules/@nestjs/core/nest-factory.d.ts","./node_modules/@nestjs/core/repl/repl.d.ts","./node_modules/@nestjs/core/repl/index.d.ts","./node_modules/@nestjs/core/router/interfaces/routes.interface.d.ts","./node_modules/@nestjs/core/router/interfaces/index.d.ts","./node_modules/@nestjs/core/router/request/request-constants.d.ts","./node_modules/@nestjs/core/router/request/index.d.ts","./node_modules/@nestjs/core/router/router-module.d.ts","./node_modules/@nestjs/core/router/index.d.ts","./node_modules/@nestjs/core/services/reflector.service.d.ts","./node_modules/@nestjs/core/services/index.d.ts","./node_modules/@nestjs/core/index.d.ts","./node_modules/@nestjs/throttler/dist/throttler-storage-record.interface.d.ts","./node_modules/@nestjs/throttler/dist/throttler-storage.interface.d.ts","./node_modules/@nestjs/throttler/dist/throttler.guard.interface.d.ts","./node_modules/@nestjs/throttler/dist/throttler-module-options.interface.d.ts","./node_modules/@nestjs/throttler/dist/throttler.decorator.d.ts","./node_modules/@nestjs/throttler/dist/throttler.exception.d.ts","./node_modules/@nestjs/throttler/dist/throttler.guard.d.ts","./node_modules/@nestjs/throttler/dist/throttler.module.d.ts","./node_modules/@nestjs/throttler/dist/throttler.providers.d.ts","./node_modules/@nestjs/throttler/dist/throttler-storage-options.interface.d.ts","./node_modules/@nestjs/throttler/dist/throttler.service.d.ts","./node_modules/@nestjs/throttler/dist/utilities.d.ts","./node_modules/@nestjs/throttler/dist/index.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/buffer/index.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/filereader.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/send/index.d.ts","./node_modules/@types/qs/index.d.ts","./node_modules/@types/range-parser/index.d.ts","./node_modules/@types/express-serve-static-core/index.d.ts","./node_modules/@types/http-errors/index.d.ts","./node_modules/@types/mime/index.d.ts","./node_modules/@types/serve-static/node_modules/@types/send/index.d.ts","./node_modules/@types/serve-static/index.d.ts","./node_modules/@types/connect/index.d.ts","./node_modules/@types/body-parser/index.d.ts","./node_modules/@types/express/index.d.ts","./node_modules/@prisma/client/runtime/library.d.ts","./node_modules/.prisma/client/index.d.ts","./node_modules/.prisma/client/default.d.ts","./node_modules/@prisma/client/default.d.ts","./src/prisma/prisma.service.ts","./src/health/health.service.ts","./src/auth/decorators/public.decorator.ts","./src/health/health.controller.ts","./src/prisma/prisma.module.ts","./src/health/health.module.ts","./node_modules/class-validator/types/validation/validationerror.d.ts","./node_modules/class-validator/types/validation/validatoroptions.d.ts","./node_modules/class-validator/types/validation-schema/validationschema.d.ts","./node_modules/class-validator/types/container.d.ts","./node_modules/class-validator/types/validation/validationarguments.d.ts","./node_modules/class-validator/types/decorator/validationoptions.d.ts","./node_modules/class-validator/types/decorator/common/allow.d.ts","./node_modules/class-validator/types/decorator/common/isdefined.d.ts","./node_modules/class-validator/types/decorator/common/isoptional.d.ts","./node_modules/class-validator/types/decorator/common/validate.d.ts","./node_modules/class-validator/types/validation/validatorconstraintinterface.d.ts","./node_modules/class-validator/types/decorator/common/validateby.d.ts","./node_modules/class-validator/types/decorator/common/validateif.d.ts","./node_modules/class-validator/types/decorator/common/validatenested.d.ts","./node_modules/class-validator/types/decorator/common/validatepromise.d.ts","./node_modules/class-validator/types/decorator/common/islatlong.d.ts","./node_modules/class-validator/types/decorator/common/islatitude.d.ts","./node_modules/class-validator/types/decorator/common/islongitude.d.ts","./node_modules/class-validator/types/decorator/common/equals.d.ts","./node_modules/class-validator/types/decorator/common/notequals.d.ts","./node_modules/class-validator/types/decorator/common/isempty.d.ts","./node_modules/class-validator/types/decorator/common/isnotempty.d.ts","./node_modules/class-validator/types/decorator/common/isin.d.ts","./node_modules/class-validator/types/decorator/common/isnotin.d.ts","./node_modules/class-validator/types/decorator/number/isdivisibleby.d.ts","./node_modules/class-validator/types/decorator/number/ispositive.d.ts","./node_modules/class-validator/types/decorator/number/isnegative.d.ts","./node_modules/class-validator/types/decorator/number/max.d.ts","./node_modules/class-validator/types/decorator/number/min.d.ts","./node_modules/class-validator/types/decorator/date/mindate.d.ts","./node_modules/class-validator/types/decorator/date/maxdate.d.ts","./node_modules/class-validator/types/decorator/string/contains.d.ts","./node_modules/class-validator/types/decorator/string/notcontains.d.ts","./node_modules/@types/validator/lib/isboolean.d.ts","./node_modules/@types/validator/lib/isemail.d.ts","./node_modules/@types/validator/lib/isfqdn.d.ts","./node_modules/@types/validator/lib/isiban.d.ts","./node_modules/@types/validator/lib/isiso31661alpha2.d.ts","./node_modules/@types/validator/lib/isiso4217.d.ts","./node_modules/@types/validator/lib/isiso6391.d.ts","./node_modules/@types/validator/lib/istaxid.d.ts","./node_modules/@types/validator/lib/isurl.d.ts","./node_modules/@types/validator/index.d.ts","./node_modules/class-validator/types/decorator/string/isalpha.d.ts","./node_modules/class-validator/types/decorator/string/isalphanumeric.d.ts","./node_modules/class-validator/types/decorator/string/isdecimal.d.ts","./node_modules/class-validator/types/decorator/string/isascii.d.ts","./node_modules/class-validator/types/decorator/string/isbase64.d.ts","./node_modules/class-validator/types/decorator/string/isbytelength.d.ts","./node_modules/class-validator/types/decorator/string/iscreditcard.d.ts","./node_modules/class-validator/types/decorator/string/iscurrency.d.ts","./node_modules/class-validator/types/decorator/string/isemail.d.ts","./node_modules/class-validator/types/decorator/string/isfqdn.d.ts","./node_modules/class-validator/types/decorator/string/isfullwidth.d.ts","./node_modules/class-validator/types/decorator/string/ishalfwidth.d.ts","./node_modules/class-validator/types/decorator/string/isvariablewidth.d.ts","./node_modules/class-validator/types/decorator/string/ishexcolor.d.ts","./node_modules/class-validator/types/decorator/string/ishexadecimal.d.ts","./node_modules/class-validator/types/decorator/string/ismacaddress.d.ts","./node_modules/class-validator/types/decorator/string/isip.d.ts","./node_modules/class-validator/types/decorator/string/isport.d.ts","./node_modules/class-validator/types/decorator/string/isisbn.d.ts","./node_modules/class-validator/types/decorator/string/isisin.d.ts","./node_modules/class-validator/types/decorator/string/isiso8601.d.ts","./node_modules/class-validator/types/decorator/string/isjson.d.ts","./node_modules/class-validator/types/decorator/string/isjwt.d.ts","./node_modules/class-validator/types/decorator/string/islowercase.d.ts","./node_modules/class-validator/types/decorator/string/ismobilephone.d.ts","./node_modules/class-validator/types/decorator/string/isiso31661alpha2.d.ts","./node_modules/class-validator/types/decorator/string/isiso31661alpha3.d.ts","./node_modules/class-validator/types/decorator/string/isiso31661numeric.d.ts","./node_modules/class-validator/types/decorator/string/ismongoid.d.ts","./node_modules/class-validator/types/decorator/string/ismultibyte.d.ts","./node_modules/class-validator/types/decorator/string/issurrogatepair.d.ts","./node_modules/class-validator/types/decorator/string/isurl.d.ts","./node_modules/class-validator/types/decorator/string/isuuid.d.ts","./node_modules/class-validator/types/decorator/string/isfirebasepushid.d.ts","./node_modules/class-validator/types/decorator/string/isuppercase.d.ts","./node_modules/class-validator/types/decorator/string/length.d.ts","./node_modules/class-validator/types/decorator/string/maxlength.d.ts","./node_modules/class-validator/types/decorator/string/minlength.d.ts","./node_modules/class-validator/types/decorator/string/matches.d.ts","./node_modules/libphonenumber-js/types.d.cts","./node_modules/libphonenumber-js/max/index.d.cts","./node_modules/class-validator/types/decorator/string/isphonenumber.d.ts","./node_modules/class-validator/types/decorator/string/ismilitarytime.d.ts","./node_modules/class-validator/types/decorator/string/ishash.d.ts","./node_modules/class-validator/types/decorator/string/isissn.d.ts","./node_modules/class-validator/types/decorator/string/isdatestring.d.ts","./node_modules/class-validator/types/decorator/string/isbooleanstring.d.ts","./node_modules/class-validator/types/decorator/string/isnumberstring.d.ts","./node_modules/class-validator/types/decorator/string/isbase32.d.ts","./node_modules/class-validator/types/decorator/string/isbic.d.ts","./node_modules/class-validator/types/decorator/string/isbtcaddress.d.ts","./node_modules/class-validator/types/decorator/string/isdatauri.d.ts","./node_modules/class-validator/types/decorator/string/isean.d.ts","./node_modules/class-validator/types/decorator/string/isethereumaddress.d.ts","./node_modules/class-validator/types/decorator/string/ishsl.d.ts","./node_modules/class-validator/types/decorator/string/isiban.d.ts","./node_modules/class-validator/types/decorator/string/isidentitycard.d.ts","./node_modules/class-validator/types/decorator/string/isisrc.d.ts","./node_modules/class-validator/types/decorator/string/islocale.d.ts","./node_modules/class-validator/types/decorator/string/ismagneturi.d.ts","./node_modules/class-validator/types/decorator/string/ismimetype.d.ts","./node_modules/class-validator/types/decorator/string/isoctal.d.ts","./node_modules/class-validator/types/decorator/string/ispassportnumber.d.ts","./node_modules/class-validator/types/decorator/string/ispostalcode.d.ts","./node_modules/class-validator/types/decorator/string/isrfc3339.d.ts","./node_modules/class-validator/types/decorator/string/isrgbcolor.d.ts","./node_modules/class-validator/types/decorator/string/issemver.d.ts","./node_modules/class-validator/types/decorator/string/isstrongpassword.d.ts","./node_modules/class-validator/types/decorator/string/istimezone.d.ts","./node_modules/class-validator/types/decorator/string/isbase58.d.ts","./node_modules/class-validator/types/decorator/string/is-tax-id.d.ts","./node_modules/class-validator/types/decorator/string/is-iso4217-currency-code.d.ts","./node_modules/class-validator/types/decorator/string/isiso6391.d.ts","./node_modules/class-validator/types/decorator/typechecker/isboolean.d.ts","./node_modules/class-validator/types/decorator/typechecker/isdate.d.ts","./node_modules/class-validator/types/decorator/typechecker/isnumber.d.ts","./node_modules/class-validator/types/decorator/typechecker/isenum.d.ts","./node_modules/class-validator/types/decorator/typechecker/isint.d.ts","./node_modules/class-validator/types/decorator/typechecker/isstring.d.ts","./node_modules/class-validator/types/decorator/typechecker/isarray.d.ts","./node_modules/class-validator/types/decorator/typechecker/isobject.d.ts","./node_modules/class-validator/types/decorator/array/arraycontains.d.ts","./node_modules/class-validator/types/decorator/array/arraynotcontains.d.ts","./node_modules/class-validator/types/decorator/array/arraynotempty.d.ts","./node_modules/class-validator/types/decorator/array/arrayminsize.d.ts","./node_modules/class-validator/types/decorator/array/arraymaxsize.d.ts","./node_modules/class-validator/types/decorator/array/arrayunique.d.ts","./node_modules/class-validator/types/decorator/object/isnotemptyobject.d.ts","./node_modules/class-validator/types/decorator/object/isinstance.d.ts","./node_modules/class-validator/types/decorator/decorators.d.ts","./node_modules/class-validator/types/validation/validationtypes.d.ts","./node_modules/class-validator/types/validation/validator.d.ts","./node_modules/class-validator/types/register-decorator.d.ts","./node_modules/class-validator/types/metadata/validationmetadataargs.d.ts","./node_modules/class-validator/types/metadata/validationmetadata.d.ts","./node_modules/class-validator/types/metadata/constraintmetadata.d.ts","./node_modules/class-validator/types/metadata/metadatastorage.d.ts","./node_modules/class-validator/types/index.d.ts","./src/products/dto/create-product.dto.ts","./src/products/dto/update-product.dto.ts","./src/common/utils/normalize-name.ts","./src/products/dto/upsert-nutrition.dto.ts","./src/categories/categories.service.ts","./src/ai/ai.service.ts","./src/products/products.service.ts","./src/products/dto/merge-products.dto.ts","./src/products/dto/update-canonical-name.dto.ts","./src/products/dto/set-tags.dto.ts","./src/products/dto/bulk-update-products.dto.ts","./src/products/dto/ai-categorize-bulk.dto.ts","./src/products/dto/set-product-status.dto.ts","./src/auth/decorators/roles.decorator.ts","./src/auth/premium-or-admin.guard.ts","./src/products/products.controller.ts","./src/ai/ai.controller.ts","./src/ai/ai.module.ts","./src/categories/categories.controller.ts","./src/categories/categories.module.ts","./src/products/products.module.ts","./src/inventory/dto/create-inventory.dto.ts","./src/inventory/dto/update-inventory.dto.ts","./src/inventory/dto/consume-inventory.dto.ts","./src/inventory/inventory.service.ts","./src/inventory/inventory.controller.ts","./src/inventory/inventory.module.ts","./node_modules/class-transformer/types/interfaces/decorator-options/expose-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/exclude-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/transform-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/type-discriminator-descriptor.interface.d.ts","./node_modules/class-transformer/types/interfaces/decorator-options/type-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/exclude-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/expose-metadata.interface.d.ts","./node_modules/class-transformer/types/enums/transformation-type.enum.d.ts","./node_modules/class-transformer/types/enums/index.d.ts","./node_modules/class-transformer/types/interfaces/target-map.interface.d.ts","./node_modules/class-transformer/types/interfaces/class-transformer-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/transform-fn-params.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/transform-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/metadata/type-metadata.interface.d.ts","./node_modules/class-transformer/types/interfaces/class-constructor.type.d.ts","./node_modules/class-transformer/types/interfaces/type-help-options.interface.d.ts","./node_modules/class-transformer/types/interfaces/index.d.ts","./node_modules/class-transformer/types/classtransformer.d.ts","./node_modules/class-transformer/types/decorators/exclude.decorator.d.ts","./node_modules/class-transformer/types/decorators/expose.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-instance-to-instance.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-instance-to-plain.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform-plain-to-instance.decorator.d.ts","./node_modules/class-transformer/types/decorators/transform.decorator.d.ts","./node_modules/class-transformer/types/decorators/type.decorator.d.ts","./node_modules/class-transformer/types/decorators/index.d.ts","./node_modules/class-transformer/types/index.d.ts","./src/recipes/dto/create-recipe.dto.ts","./src/recipes/dto/create-ingredient.dto.ts","./src/recipes/dto/parse-markdown.dto.ts","./node_modules/sharp/lib/index.d.ts","./node_modules/uuid/dist/cjs/types.d.ts","./node_modules/uuid/dist/cjs/max.d.ts","./node_modules/uuid/dist/cjs/nil.d.ts","./node_modules/uuid/dist/cjs/parse.d.ts","./node_modules/uuid/dist/cjs/stringify.d.ts","./node_modules/uuid/dist/cjs/v1.d.ts","./node_modules/uuid/dist/cjs/v1tov6.d.ts","./node_modules/uuid/dist/cjs/v35.d.ts","./node_modules/uuid/dist/cjs/v3.d.ts","./node_modules/uuid/dist/cjs/v4.d.ts","./node_modules/uuid/dist/cjs/v5.d.ts","./node_modules/uuid/dist/cjs/v6.d.ts","./node_modules/uuid/dist/cjs/v6tov1.d.ts","./node_modules/uuid/dist/cjs/v7.d.ts","./node_modules/uuid/dist/cjs/validate.d.ts","./node_modules/uuid/dist/cjs/version.d.ts","./node_modules/uuid/dist/cjs/index.d.ts","./src/common/utils/download-image.ts","./src/common/utils/recipe-parser.ts","./src/common/utils/units.ts","./src/recipes/recipes.service.ts","./src/auth/decorators/current-user.decorator.ts","./src/recipes/dto/share-recipe.dto.ts","./src/recipes/dto/set-recipe-visibility.dto.ts","./src/recipes/recipes.controller.ts","./src/recipes/recipes.module.ts","./node_modules/@nestjs/platform-express/interfaces/nest-express-body-parser-options.interface.d.ts","./node_modules/@nestjs/platform-express/interfaces/nest-express-body-parser.interface.d.ts","./node_modules/@nestjs/platform-express/interfaces/serve-static-options.interface.d.ts","./node_modules/@nestjs/platform-express/adapters/express-adapter.d.ts","./node_modules/@nestjs/platform-express/adapters/index.d.ts","./node_modules/@nestjs/platform-express/interfaces/nest-express-application.interface.d.ts","./node_modules/@nestjs/platform-express/interfaces/index.d.ts","./node_modules/@nestjs/platform-express/multer/interfaces/multer-options.interface.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/any-files.interceptor.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/file-fields.interceptor.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/file.interceptor.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/files.interceptor.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/no-files.interceptor.d.ts","./node_modules/@nestjs/platform-express/multer/interceptors/index.d.ts","./node_modules/@nestjs/platform-express/multer/interfaces/files-upload-module.interface.d.ts","./node_modules/@nestjs/platform-express/multer/interfaces/index.d.ts","./node_modules/@nestjs/platform-express/multer/multer.module.d.ts","./node_modules/@nestjs/platform-express/multer/index.d.ts","./node_modules/@nestjs/platform-express/index.d.ts","./node_modules/@types/multer/index.d.ts","./src/quick-import/dto/quick-import.dto.ts","./src/quick-import/quick-import.service.ts","./src/quick-import/quick-import.controller.ts","./src/quick-import/quick-import.module.ts","./src/pantry/dto/create-pantry-item.dto.ts","./src/pantry/pantry.service.ts","./src/pantry/pantry.controller.ts","./src/pantry/pantry.module.ts","./src/meal-plan/dto/create-meal-plan-entry.dto.ts","./src/meal-plan/meal-plan.service.ts","./src/meal-plan/meal-plan.controller.ts","./src/meal-plan/meal-plan.module.ts","./src/receipt-import/dto/parsed-receipt-item.dto.ts","./src/receipt-import/receipt-import.service.ts","./node_modules/@nestjs/passport/dist/abstract.strategy.d.ts","./node_modules/@nestjs/passport/dist/interfaces/auth-module.options.d.ts","./node_modules/@nestjs/passport/dist/interfaces/type.interface.d.ts","./node_modules/@nestjs/passport/dist/interfaces/index.d.ts","./node_modules/@nestjs/passport/dist/auth.guard.d.ts","./node_modules/@nestjs/passport/dist/passport.module.d.ts","./node_modules/@types/passport/index.d.ts","./node_modules/@nestjs/passport/dist/passport/passport.serializer.d.ts","./node_modules/@nestjs/passport/dist/passport/passport.strategy.d.ts","./node_modules/@nestjs/passport/dist/index.d.ts","./node_modules/@nestjs/passport/index.d.ts","./src/receipt-import/receipt-import.controller.ts","./src/receipt-import/receipt-import.module.ts","./src/receipt-alias/dto/create-receipt-alias.dto.ts","./src/receipt-alias/receipt-alias.service.ts","./src/receipt-alias/receipt-alias.controller.ts","./src/receipt-alias/receipt-alias.module.ts","./node_modules/@types/jsonwebtoken/index.d.ts","./node_modules/@nestjs/jwt/dist/interfaces/jwt-module-options.interface.d.ts","./node_modules/@nestjs/jwt/dist/interfaces/index.d.ts","./node_modules/@nestjs/jwt/dist/jwt.errors.d.ts","./node_modules/@nestjs/jwt/dist/jwt.module.d.ts","./node_modules/@nestjs/jwt/dist/jwt.service.d.ts","./node_modules/@nestjs/jwt/dist/index.d.ts","./node_modules/@nestjs/jwt/index.d.ts","./node_modules/@types/bcryptjs/index.d.ts","./src/users/users.service.ts","./src/auth/dto/register.dto.ts","./src/auth/dto/login.dto.ts","./src/auth/auth.service.ts","./src/auth/auth.controller.ts","./node_modules/@types/passport-strategy/index.d.ts","./node_modules/@types/passport-jwt/index.d.ts","./src/auth/jwt.strategy.ts","./src/users/users.controller.ts","./src/users/admin-bootstrap.service.ts","./src/users/users.module.ts","./src/auth/auth.module.ts","./src/user-products/dto/upsert-user-product.dto.ts","./src/user-products/user-products.service.ts","./src/user-products/user-products.controller.ts","./src/user-products/user-products.module.ts","./src/auth/jwt-auth.guard.ts","./src/auth/roles.guard.ts","./src/app.module.ts","./src/common/filters/global-exception.filter.ts","./node_modules/helmet/index.d.cts","./src/main.ts","./src/common/utils/normalize-name.spec.ts","./src/common/utils/recipe-parser.spec.ts","./src/receipt-import/receipt-import.service.spec.ts","./src/recipes/recipes.service.spec.ts","./src/recipes/dto/create-recipe-ingredient.dto.ts","./node_modules/@jest/expect-utils/build/index.d.ts","./node_modules/chalk/index.d.ts","./node_modules/@sinclair/typebox/typebox.d.ts","./node_modules/@jest/schemas/build/index.d.ts","./node_modules/pretty-format/build/index.d.ts","./node_modules/jest-diff/build/index.d.ts","./node_modules/jest-matcher-utils/build/index.d.ts","./node_modules/expect/build/index.d.ts","./node_modules/@types/jest/index.d.ts"],"fileIdsList":[[515,564,581,582,627],[515,564,581,582,626],[515,564,581,582],[515,564,581,582,950],[308,515,564,581,582],[403,515,564,581,582],[58,309,310,311,312,313,314,315,316,317,318,319,320,321,515,564,581,582],[261,295,515,564,581,582],[268,515,564,581,582],[258,308,403,515,564,581,582],[326,327,328,329,330,331,332,333,515,564,581,582],[263,515,564,581,582],[308,403,515,564,581,582],[322,325,334,515,564,581,582],[323,324,515,564,581,582],[299,515,564,581,582],[263,264,265,266,515,564,581,582],[336,515,564,581,582],[281,515,564,581,582],[336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,515,564,581,582],[364,515,564,581,582],[359,360,515,564,581,582],[361,363,515,564,581,582,596],[57,267,308,335,358,363,365,372,395,400,402,515,564,581,582],[63,261,515,564,581,582],[62,515,564,581,582],[63,253,254,435,440,515,564,581,582],[253,261,515,564,581,582],[62,252,515,564,581,582],[261,374,515,564,581,582],[255,376,515,564,581,582],[252,256,515,564,581,582],[62,308,515,564,581,582],[260,261,515,564,581,582],[273,515,564,581,582],[275,276,277,278,279,515,564,581,582],[267,515,564,581,582],[267,268,283,287,515,564,581,582],[281,282,288,289,290,515,564,581,582],[59,60,61,62,63,253,254,255,256,257,258,259,260,261,262,268,273,274,280,287,291,292,293,295,303,304,305,306,307,515,564,581,582],[286,515,564,581,582],[269,270,271,272,515,564,581,582],[261,269,270,515,564,581,582],[261,267,268,515,564,581,582],[261,271,515,564,581,582],[261,299,515,564,581,582],[294,296,297,298,299,300,301,302,515,564,581,582],[59,261,515,564,581,582],[295,515,564,581,582],[59,261,294,298,300,515,564,581,582],[270,515,564,581,582],[296,515,564,581,582],[261,295,296,297,515,564,581,582],[285,515,564,581,582],[261,265,285,303,515,564,581,582],[283,284,286,515,564,581,582],[257,259,268,274,283,288,304,305,308,515,564,581,582],[63,257,259,262,304,305,515,564,581,582],[266,515,564,581,582],[252,515,564,581,582],[285,308,366,370,515,564,581,582],[370,371,515,564,581,582],[308,366,515,564,581,582],[308,366,367,515,564,581,582],[367,368,515,564,581,582],[367,368,369,515,564,581,582],[262,515,564,581,582],[387,388,515,564,581,582],[387,515,564,581,582],[388,389,390,391,392,393,515,564,581,582],[386,515,564,581,582],[378,388,515,564,581,582],[388,389,390,391,392,515,564,581,582],[262,387,388,391,515,564,581,582],[373,379,380,381,382,383,384,385,394,515,564,581,582],[262,308,379,515,564,581,582],[262,378,515,564,581,582],[262,378,403,515,564,581,582],[255,261,262,374,375,376,377,378,515,564,581,582],[252,308,374,375,396,515,564,581,582],[308,374,515,564,581,582],[398,515,564,581,582],[335,396,515,564,581,582],[396,397,399,515,564,581,582],[285,362,515,564,581,582],[294,515,564,581,582],[267,308,515,564,581,582],[401,515,564,581,582],[283,287,308,403,515,564,581,582],[404,515,564,581,582],[308,403,424,425,515,564,581,582],[406,515,564,581,582],[403,418,423,424,515,564,581,582],[428,429,515,564,581,582],[63,308,419,424,438,515,564,581,582],[403,405,431,515,564,581,582],[62,403,432,435,515,564,581,582],[308,419,424,426,437,439,443,515,564,581,582],[62,441,442,515,564,581,582],[432,515,564,581,582],[252,308,403,446,515,564,581,582],[308,403,419,424,426,438,515,564,581,582],[445,447,448,515,564,581,582],[308,424,515,564,581,582],[424,515,564,581,582],[308,403,446,515,564,581,582],[62,308,403,515,564,581,582],[308,403,418,419,424,444,446,449,452,457,458,471,472,515,564,581,582],[252,404,515,564,581,582],[431,434,473,515,564,581,582],[458,470,515,564,581,582],[57,405,426,427,430,433,465,470,474,477,481,482,483,485,487,493,495,515,564,581,582],[308,403,412,420,423,424,515,564,581,582],[308,416,515,564,581,582],[308,403,406,415,416,417,418,423,424,426,496,515,564,581,582],[418,419,422,424,460,469,515,564,581,582],[308,403,411,423,424,515,564,581,582],[459,515,564,581,582],[403,419,424,515,564,581,582],[403,412,419,423,464,515,564,581,582],[308,403,406,411,423,515,564,581,582],[403,417,418,422,462,466,467,468,515,564,581,582],[403,412,419,420,421,423,424,515,564,581,582],[261,403,515,564,581,582],[308,406,419,422,424,515,564,581,582],[423,515,564,581,582],[408,409,410,419,423,424,463,515,564,581,582],[415,464,475,476,515,564,581,582],[403,406,424,515,564,581,582],[403,406,515,564,581,582],[407,408,409,410,413,415,515,564,581,582],[412,515,564,581,582],[414,415,515,564,581,582],[403,407,408,409,410,413,414,515,564,581,582],[450,451,515,564,581,582],[308,419,424,426,438,515,564,581,582],[461,515,564,581,582],[292,515,564,581,582],[273,308,478,479,515,564,581,582],[480,515,564,581,582],[308,426,515,564,581,582],[308,419,426,515,564,581,582],[286,308,403,412,419,420,421,423,424,515,564,581,582],[283,285,308,403,405,419,426,464,482,515,564,581,582],[286,287,403,404,484,515,564,581,582],[454,455,456,515,564,581,582],[403,453,515,564,581,582],[486,515,564,581,582],[403,515,564,581,582,593],[489,491,492,515,564,581,582],[488,515,564,581,582],[490,515,564,581,582],[403,418,423,489,515,564,581,582],[436,515,564,581,582],[308,403,406,419,423,424,426,461,462,464,465,515,564,581,582],[494,515,564,581,582],[515,564,581,582,912,914,915,916,917],[515,564,581,582,913],[403,515,564,581,582,614,912],[403,515,564,581,582,913],[515,564,581,582,614,912,914],[515,564,581,582,918],[403,515,564,581,582,896,898],[515,564,581,582,895,898,899,900,902,903],[515,564,581,582,896,897],[403,515,564,581,582,896],[515,564,581,582,901],[515,564,581,582,898],[515,564,581,582,904],[283,287,308,403,404,515,564,578,580,581,582,861,862,863],[515,564,581,582,864],[515,564,581,582,865,867,878],[515,564,581,582,861,862,866],[403,515,564,578,580,581,582,625,861,862,863],[515,564,578,581,582],[515,564,581,582,874,876,877],[403,515,564,581,582,868],[515,564,581,582,869,870,871,872,873],[308,515,564,581,582,868],[515,564,581,582,875],[403,515,564,581,582,875],[498,499,500,501,502,503,504,505,507,508,515,564,581,582],[308,498,499,515,564,581,582],[497,515,564,581,582],[500,515,564,581,582],[403,496,498,499,500,515,564,581,582],[403,497,500,515,564,581,582],[403,500,515,564,581,582],[403,498,500,515,564,581,582],[403,497,498,506,515,564,581,582],[515,564,581,582,628],[515,564,578,581,582,614,623],[515,564,578,581,582,614],[515,564,575,578,581,582,614,615,616,617],[515,564,581,582,616,618,622,624],[515,564,581,582,952,955],[515,564,569,581,582,614],[515,564,581,582,596,625],[515,561,562,564,581,582],[515,563,564,581,582],[564,581,582],[515,564,569,581,582,599],[515,564,565,570,575,581,582,584,596,607],[515,564,565,566,575,581,582,584],[510,511,512,515,564,581,582],[515,564,567,581,582,608],[515,564,568,569,576,581,582,585],[515,564,569,581,582,596,604],[515,564,570,572,575,581,582,584],[515,563,564,571,581,582],[515,564,572,573,581,582],[515,564,574,575,581,582],[515,563,564,575,581,582],[515,564,575,576,577,581,582,596,607],[515,564,575,576,577,581,582,591,596,599],[515,557,564,572,575,578,581,582,584,596,607],[515,564,575,576,578,579,581,582,584,596,604,607],[515,564,578,580,581,582,596,604,607],[513,514,515,516,517,518,519,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613],[515,564,575,581,582],[515,564,581,582,583,607],[515,564,572,575,581,582,584,596],[515,564,581,582,585],[515,564,581,582,586],[515,563,564,581,582,587],[515,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613],[515,564,581,582,589],[515,564,581,582,590],[515,564,575,581,582,591,592],[515,564,581,582,591,593,608,610],[515,564,576,581,582],[515,564,575,581,582,596,597,599],[515,564,581,582,598,599],[515,564,581,582,596,597],[515,564,581,582,599],[515,564,581,582,600],[515,561,564,581,582,596,601,607],[515,564,575,581,582,602,603],[515,564,581,582,602,603],[515,564,569,581,582,584,596,604],[515,564,581,582,605],[515,564,581,582,584,606],[515,564,578,581,582,590,607],[515,564,569,581,582,608],[515,564,581,582,596,609],[515,564,581,582,583,610],[515,564,581,582,611],[515,557,564,581,582],[515,557,564,575,577,581,582,587,596,599,607,609,610,612],[515,564,581,582,596,613],[515,564,581,582,912,926],[515,564,581,582,625,901],[515,564,578,581,582,625],[515,564,576,581,582,596,614],[515,564,578,581,582,614,619,621],[515,564,576,581,582,596,614,620],[515,564,581,582,669,670,671,672,673,674,675,676,677],[515,564,581,582,820],[515,564,581,582,822,823,824,825,826,827,828],[515,564,581,582,811],[515,564,581,582,812,820,821,829],[515,564,581,582,813],[515,564,581,582,807],[515,564,581,582,804,805,806,807,808,809,810,813,814,815,816,817,818,819],[515,564,581,582,812,814],[515,564,581,582,815,820],[515,564,581,582,641],[515,564,581,582,640,641,646],[515,564,581,582,642,643,644,645,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767],[515,564,581,582,641,678],[515,564,581,582,641,672],[515,564,581,582,641,719],[515,564,581,582,640],[515,564,581,582,636,637,638,639,640,641,646,768,769,770,771,775],[515,564,581,582,646],[515,564,581,582,638,773,774],[515,564,581,582,640,772],[515,564,581,582,641,646],[515,564,581,582,636,637],[515,564,581,582,948,954],[515,564,581,582,952],[515,564,581,582,949,953],[515,564,581,582,718],[515,564,581,582,951],[64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,80,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,133,134,135,136,137,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,183,184,185,187,196,198,199,200,201,202,203,205,206,208,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,515,564,581,582],[109,515,564,581,582],[65,68,515,564,581,582],[67,515,564,581,582],[67,68,515,564,581,582],[64,65,66,68,515,564,581,582],[65,67,68,225,515,564,581,582],[68,515,564,581,582],[64,67,109,515,564,581,582],[67,68,225,515,564,581,582],[67,233,515,564,581,582],[65,67,68,515,564,581,582],[77,515,564,581,582],[100,515,564,581,582],[121,515,564,581,582],[67,68,109,515,564,581,582],[68,116,515,564,581,582],[67,68,109,127,515,564,581,582],[67,68,127,515,564,581,582],[68,168,515,564,581,582],[68,109,515,564,581,582],[64,68,186,515,564,581,582],[64,68,187,515,564,581,582],[209,515,564,581,582],[193,195,515,564,581,582],[204,515,564,581,582],[193,515,564,581,582],[64,68,186,193,194,515,564,581,582],[186,187,195,515,564,581,582],[207,515,564,581,582],[64,68,193,194,195,515,564,581,582],[66,67,68,515,564,581,582],[64,68,515,564,581,582],[65,67,187,188,189,190,515,564,581,582],[109,187,188,189,190,515,564,581,582],[187,189,515,564,581,582],[67,188,189,191,192,196,515,564,581,582],[64,67,515,564,581,582],[68,211,515,564,581,582],[69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,117,118,119,120,121,122,123,124,125,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,515,564,581,582],[197,515,564,581,582],[515,564,581,582,596,614],[515,529,533,564,581,582,607],[515,529,564,581,582,596,607],[515,524,564,581,582],[515,526,529,564,581,582,604,607],[515,564,581,582,584,604],[515,564,581,582,614],[515,524,564,581,582,614],[515,526,529,564,581,582,584,607],[515,521,522,525,528,564,575,581,582,596,607],[515,529,536,564,581,582],[515,521,527,564,581,582],[515,529,550,551,564,581,582],[515,525,529,564,581,582,599,607,614],[515,550,564,581,582,614],[515,523,524,564,581,582,614],[515,529,564,581,582],[515,523,524,525,526,527,528,529,530,531,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,551,552,553,554,555,556,564,581,582],[515,529,544,564,581,582],[515,529,536,537,564,581,582],[515,527,529,537,538,564,581,582],[515,528,564,581,582],[515,521,524,529,564,581,582],[515,529,533,537,538,564,581,582],[515,533,564,581,582],[515,527,529,532,564,581,582,607],[515,521,526,529,536,564,581,582],[515,564,581,582,596],[515,524,529,550,564,581,582,612,614],[515,564,581,582,835,836,837,838,839,840,841,843,844,845,846,847,848,849,850],[515,564,581,582,835],[515,564,581,582,835,842],[403,515,564,581,582,632,782],[403,515,564,581,582,782,793],[403,515,564,581,582,781],[403,496,509,515,564,581,582,634,635,794,796,797,803,860,884,888,892,907,911,931,932,936,937,938],[403,509,515,564,581,582,632,922,923,924],[403,515,564,581,582,905,919,924,925,928,931],[403,515,564,581,582,919,920,921,922,923],[515,564,581,582,776],[252,403,496,515,564,581,582,632,905],[403,515,564,581,582,905,927],[403,496,515,564,581,582,790],[403,515,564,581,582,632,781],[403,515,564,581,582,634,781,795],[403,515,564,581,582,630],[403,515,564,581,582,625],[515,564,576,581,582,586,834,851],[515,564,581,582,779],[515,564,581,582,853],[403,515,564,581,582,625,631,632],[403,515,564,581,582,631,633,634],[403,515,564,581,582,798,799,800,801],[403,515,564,581,582,634,801,802],[403,515,564,581,582,629,630,798,799,800],[403,496,515,564,581,582,939,940,941],[403,515,564,581,582,856,889,890],[403,515,564,581,582,634,890,891],[403,515,564,581,582,630,889],[403,515,564,581,582,856,885,886],[403,515,564,581,582,634,886,887],[403,515,564,581,582,630,885],[403,515,564,581,582,629],[403,509,515,564,581,582,632,777,778,780,781,782,783,784,785,786,787,788,789,790,791],[403,515,564,581,582,783,792,794,796],[403,515,564,581,582,630,777,778,779,780,781,782],[403,509,515,564,581,582,879,880,881,882],[403,515,564,581,582,882,883],[403,515,564,581,582,852],[403,515,564,581,582,856,908,909],[403,515,564,581,582,634,909,910],[403,515,564,581,582,630,908],[515,564,581,582,782],[403,509,515,564,581,582,879,880,893,894,905],[403,515,564,581,582,634,794,796,894,906],[515,564,581,582,781,782,894],[403,515,564,581,582,630,781,782,893],[515,564,581,582,776,830],[403,515,564,581,582,776,831,832,833,855,856,857,858],[403,515,564,581,582,634,794,855,859],[515,564,581,582,854],[403,515,564,577,581,582,586,629,630,782,831,832,833,852,853,854],[403,515,564,581,582,856,933,934],[403,515,564,581,582,634,934,935],[403,515,564,581,582,630,933],[403,515,564,581,582,630,920],[403,515,564,581,582,776,790,856,921],[403,515,564,581,582,634,921,929,930],[403,515,564,569,581,582,630,920]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a66df3ab5de5cfcda11538cffddd67ff6a174e003788e270914c1e0248483cf","impliedFormat":1},{"version":"8d6d51a5118d000ed3bfe6e1dd1335bebfff3fef23cd2af2f84a24d30f90cc90","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d8dedbec739bc79642c1e96e9bfc0b83b25b104a0486aebf016fc7b85b39f48","impliedFormat":1},{"version":"e89535c3ec439608bcd0f68af555d0e5ddf121c54abe69343549718bd7506b9c","impliedFormat":1},{"version":"622a984b60c294ffb2f9152cf1d4d12e91d2b733d820eec949cf54d63a3c1025","impliedFormat":1},{"version":"81aae92abdeaccd9c1723cef39232c90c1aed9d9cf199e6e2a523b7d8e058a11","impliedFormat":1},{"version":"a63a6c6806a1e519688ef7bd8ca57be912fc0764485119dbd923021eb4e79665","impliedFormat":1},{"version":"75b57b109d774acca1e151df21cf5cb54c7a1df33a273f0457b9aee4ebd36fb9","impliedFormat":1},{"version":"073ca26c96184db9941b5ec0ddea6981c9b816156d9095747809e524fdd90e35","impliedFormat":1},{"version":"e41d17a2ec23306d953cda34e573ed62954ca6ea9b8c8b74e013d07a6886ce47","impliedFormat":1},{"version":"241bd4add06f06f0699dcd58f3b334718d85e3045d9e9d4fa556f11f4d1569c1","impliedFormat":1},{"version":"2ae3787e1498b20aad1b9c2ee9ea517ec30e89b70d242d8e3e52d1e091039695","impliedFormat":1},{"version":"c7c72c4cffb1bc83617eefed71ed68cc89df73cab9e19507ccdecb3e72b4967e","affectsGlobalScope":true,"impliedFormat":1},{"version":"b8bff8a60af0173430b18d9c3e5c443eaa3c515617210c0c7b3d2e1743c19ecb","impliedFormat":1},{"version":"38b38db08e7121828294dec10957a7a9ff263e33e2a904b346516d4a4acca482","impliedFormat":1},{"version":"a76ebdf2579e68e4cfe618269c47e5a12a4e045c2805ed7f7ab37af8daa6b091","impliedFormat":1},{"version":"8a2aaea564939c22be05d665cc955996721bad6d43148f8fa21ae8f64afecd37","impliedFormat":1},{"version":"e59d36b7b6e8ba2dd36d032a5f5c279d2460968c8b4e691ca384f118fb09b52a","impliedFormat":1},{"version":"e96885c0684c9042ec72a9a43ef977f6b4b4a2728f4b9e737edcbaa0c74e5bf6","impliedFormat":1},{"version":"95950a187596e206d32d5d9c7b932901088c65ed8f9040e614aa8e321e0225ef","impliedFormat":1},{"version":"89e061244da3fc21b7330f4bd32f47c1813dd4d7f1dc3d0883d88943f035b993","impliedFormat":1},{"version":"e46558c2e04d06207b080138678020448e7fc201f3d69c2601b0d1456105f29a","impliedFormat":1},{"version":"71549375db52b1163411dba383b5f4618bdf35dc57fa327a1c7d135cf9bf67d1","impliedFormat":1},{"version":"7e6b2d61d6215a4e82ea75bc31a80ebb8ad0c2b37a60c10c70dd671e8d9d6d5d","impliedFormat":1},{"version":"78bea05df2896083cca28ed75784dde46d4b194984e8fc559123b56873580a23","impliedFormat":1},{"version":"5dd04ced37b7ea09f29d277db11f160df7fd73ba8b9dba86cb25552e0653a637","impliedFormat":1},{"version":"f74b81712e06605677ae1f061600201c425430151f95b5ef4d04387ad7617e6a","impliedFormat":1},{"version":"9a72847fcf4ac937e352d40810f7b7aec7422d9178451148296cf1aa19467620","impliedFormat":1},{"version":"3ae18f60e0b96fa1e025059b7d25b3247ba4dcb5f4372f6d6e67ce2adac74eac","impliedFormat":1},{"version":"2b9260f44a2e071450ae82c110f5dc8f330c9e5c3e85567ed97248330f2bf639","impliedFormat":1},{"version":"4f196e13684186bda6f5115fc4677a87cf84a0c9c4fc17b8f51e0984f3697b6d","impliedFormat":1},{"version":"61419f2c5822b28c1ea483258437c1faab87d00c6f84481aa22afb3380d8e9a4","impliedFormat":1},{"version":"64479aee03812264e421c0bf5104a953ca7b02740ba80090aead1330d0effe91","impliedFormat":1},{"version":"0521108c9f8ddb17654a0a54dae6ba9667c99eddccfd6af5748113e022d1c37a","impliedFormat":1},{"version":"c5570e504be103e255d80c60b56c367bf45d502ca52ee35c55dec882f6563b5c","impliedFormat":1},{"version":"ee764e6e9a7f2b987cc1a2c0a9afd7a8f4d5ebc4fdb66ad557a7f14a8c2bd320","impliedFormat":1},{"version":"0520b5093712c10c6ef23b5fea2f833bf5481771977112500045e5ea7e8e2b69","impliedFormat":1},{"version":"5c3cf26654cf762ac4d7fd7b83f09acfe08eef88d2d6983b9a5a423cb4004ca3","impliedFormat":1},{"version":"e60fa19cf7911c1623b891155d7eb6b7e844e9afdf5738e3b46f3b687730a2bd","impliedFormat":1},{"version":"b1fd72ff2bb0ba91bb588f3e5329f8fc884eb859794f1c4657a2bfa122ae54d0","impliedFormat":1},{"version":"6cf42a4f3cfec648545925d43afaa8bb364ac10a839ffed88249da109361b275","impliedFormat":1},{"version":"d7058e75920120b142a9d57be25562a3cd9a936269fd52908505f530105f2ec4","impliedFormat":1},{"version":"6df52b70d7f7702202f672541a5f4a424d478ee5be51a9d37b8ccbe1dbf3c0f2","impliedFormat":1},{"version":"0ca7f997e9a4d8985e842b7c882e521b6f63233c4086e9fe79dd7a9dc4742b5e","impliedFormat":1},{"version":"91046b5c6b55d3b194c81fd4df52f687736fad3095e9d103ead92bb64dc160ee","impliedFormat":1},{"version":"db5704fdad56c74dfc5941283c1182ed471bd17598209d3ac4a49faa72e43cfc","impliedFormat":1},{"version":"758e8e89559b02b81bc0f8fd395b17ad5aff75490c862cbe369bb1a3d1577c40","impliedFormat":1},{"version":"2ee64342c077b1868f1834c063f575063051edd6e2964257d34aad032d6b657c","impliedFormat":1},{"version":"6f6b4b3d670b6a5f0e24ea001c1b3d36453c539195e875687950a178f1730fa7","impliedFormat":1},{"version":"a472a1d3f25ce13a1d44911cd3983956ac040ce2018e155435ea34afb25f864c","impliedFormat":1},{"version":"b48b83a86dd9cfe36f8776b3ff52fcd45b0e043c0538dc4a4b149ba45fe367b9","impliedFormat":1},{"version":"792de5c062444bd2ee0413fb766e57e03cce7cdaebbfc52fc0c7c8e95069c96b","impliedFormat":1},{"version":"a79e3e81094c7a04a885bad9b049c519aace53300fb8a0fe4f26727cb5a746ce","impliedFormat":1},{"version":"93181bac0d90db185bb730c95214f6118ae997fe836a98a49664147fbcaf1988","impliedFormat":1},{"version":"8a4e89564d8ea66ad87ee3762e07540f9f0656a62043c910d819b4746fc429c5","impliedFormat":1},{"version":"b9011d99942889a0f95e120d06b698c628b0b6fdc3e6b7ecb459b97ed7d5bcc6","impliedFormat":1},{"version":"4d639cbbcc2f8f9ce6d55d5d503830d6c2556251df332dc5255d75af53c8a0e7","impliedFormat":1},{"version":"cdb48277f600ab5f429ecf1c5ea046683bc6b9f73f3deab9a100adac4b34969c","impliedFormat":1},{"version":"75be84956a29040a1afbe864c0a7a369dfdb739380072484eff153905ef867ee","impliedFormat":1},{"version":"b06b4adc2ae03331a92abd1b19af8eb91ec2bf8541747ee355887a167d53145e","impliedFormat":1},{"version":"c54166a85bd60f86d1ebb90ce0117c0ecb850b8a33b366691629fdf26f1bbbd8","impliedFormat":1},{"version":"0d417c15c5c635384d5f1819cc253a540fe786cc3fda32f6a2ae266671506a21","impliedFormat":1},{"version":"80f23f1d60fbed356f726b3b26f9d348dddbb34027926d10d59fad961e70a730","impliedFormat":1},{"version":"cb59317243a11379a101eb2f27b9df1022674c3df1df0727360a0a3f963f523b","impliedFormat":1},{"version":"cc20bb2227dd5de0aab0c8d697d1572f8000550e62c7bf5c92f212f657dd88c5","impliedFormat":1},{"version":"06b8a7d46195b6b3980e523ef59746702fd210b71681a83a5cf73799623621f9","impliedFormat":1},{"version":"860e4405959f646c101b8005a191298b2381af8f33716dc5f42097e4620608f8","impliedFormat":1},{"version":"f7e32adf714b8f25d3c1783473abec3f2e82d5724538d8dcf6f51baaaff1ca7a","impliedFormat":1},{"version":"d0da80c845999a16c24d0783033fb5366ada98df17867c98ad433ede05cd87fd","impliedFormat":1},{"version":"bfbf80f9cd4558af2d7b2006065340aaaced15947d590045253ded50aabb9bc5","impliedFormat":1},{"version":"fd9a991b51870325e46ebb0e6e18722d313f60cd8e596e645ec5ac15b96dbf4e","impliedFormat":1},{"version":"c3bd2b94e4298f81743d92945b80e9b56c1cdfb2bef43c149b7106a2491b1fc9","impliedFormat":1},{"version":"a246cce57f558f9ebaffd55c1e5673da44ea603b4da3b2b47eb88915d30a9181","impliedFormat":1},{"version":"d993eacc103c5a065227153c9aae8acea3a4322fe1a169ee7c70b77015bf0bb2","impliedFormat":1},{"version":"fc2b03d0c042aa1627406e753a26a1eaad01b3c496510a78016822ef8d456bb6","impliedFormat":1},{"version":"063c7ebbe756f0155a8b453f410ca6b76ffa1bbc1048735bcaf9c7c81a1ce35f","impliedFormat":1},{"version":"314e402cd481370d08f63051ae8b8c8e6370db5ee3b8820eeeaaf8d722a6dac6","impliedFormat":1},{"version":"9669075ac38ce36b638b290ba468233980d9f38bdc62f0519213b2fd3e2552ec","impliedFormat":1},{"version":"4d123de012c24e2f373925100be73d50517ac490f9ed3578ac82d0168bfbd303","impliedFormat":1},{"version":"656c9af789629aa36b39092bee3757034009620439d9a39912f587538033ce28","impliedFormat":1},{"version":"3ac3f4bdb8c0905d4c3035d6f7fb20118c21e8a17bee46d3735195b0c2a9f39f","impliedFormat":1},{"version":"1f453e6798ed29c86f703e9b41662640d4f2e61337007f27ac1c616f20093f69","impliedFormat":1},{"version":"af43b7871ff21c62bf1a54ec5c488e31a8d3408d5b51ff2e9f8581b6c55f2fc7","impliedFormat":1},{"version":"70550511d25cbb0b6a64dcac7fffc3c1397fd4cbeb6b23ccc7f9b794ab8a6954","impliedFormat":1},{"version":"af0fbf08386603a62f2a78c42d998c90353b1f1d22e05a384545f7accf881e0a","impliedFormat":1},{"version":"cefc20054d20b85b534206dbcedd509bb74f87f3d8bc45c58c7be3a76caa45e1","impliedFormat":1},{"version":"ad6eee4877d0f7e5244d34bc5026fd6e9cf8e66c5c79416b73f9f6ebf132f924","impliedFormat":1},{"version":"4888fd2bcfee9a0ce89d0df860d233e0cee8ee9c479b6bd5a5d5f9aae98342fe","impliedFormat":1},{"version":"f4749c102ced952aa6f40f0b579865429c4869f6d83df91000e98005476bee87","impliedFormat":1},{"version":"56654d2c5923598384e71cb808fac2818ca3f07dd23bb018988a39d5e64f268b","impliedFormat":1},{"version":"8b6719d3b9e65863da5390cb26994602c10a315aa16e7d70778a63fee6c4c079","impliedFormat":1},{"version":"05f56cd4b929977d18df8f3d08a4c929a2592ef5af083e79974b20a063f30940","impliedFormat":1},{"version":"547d3c406a21b30e2b78629ecc0b2ddaf652d9e0bdb2d59ceebce5612906df33","impliedFormat":1},{"version":"b3a4f9385279443c3a5568ec914a9492b59a723386161fd5ef0619d9f8982f97","impliedFormat":1},{"version":"3fe66aba4fbe0c3ba196a4f9ed2a776fe99dc4d1567a558fb11693e9fcc4e6ed","impliedFormat":1},{"version":"140eef237c7db06fc5adcb5df434ee21e81ee3a6fd57e1a75b8b3750aa2df2d8","impliedFormat":1},{"version":"0944ec553e4744efae790c68807a461720cff9f3977d4911ac0d918a17c9dd99","impliedFormat":1},{"version":"cb46b38d5e791acaa243bf342b8b5f8491639847463ac965b93896d4fb0af0d9","impliedFormat":1},{"version":"7c7d9e116fe51100ff766703e6b5e4424f51ad8977fe474ddd8d0959aa6de257","impliedFormat":1},{"version":"af70a2567e586be0083df3938b6a6792e6821363d8ef559ad8d721a33a5bcdaf","impliedFormat":1},{"version":"006cff3a8bcb92d77953f49a94cd7d5272fef4ab488b9052ef82b6a1260d870b","impliedFormat":1},{"version":"7d44bfdc8ee5e9af70738ff652c622ae3ad81815e63ab49bdc593d34cb3a68e5","impliedFormat":1},{"version":"339814517abd4dbc7b5f013dfd3b5e37ef0ea914a8bbe65413ecffd668792bc6","impliedFormat":1},{"version":"34d5bc0a6958967ec237c99f980155b5145b76e6eb927c9ffc57d8680326b5d8","impliedFormat":1},{"version":"9eae79b70c9d8288032cbe1b21d0941f6bd4f315e14786b2c1d10bccc634e897","impliedFormat":1},{"version":"18ce015ed308ea469b13b17f99ce53bbb97975855b2a09b86c052eefa4aa013a","impliedFormat":1},{"version":"5a931bc4106194e474be141e0bc1046629510dc95b9a0e4b02a3783847222965","impliedFormat":1},{"version":"5e5f371bf23d5ced2212a5ff56675aefbd0c9b3f4d4fdda1b6123ac6e28f058c","impliedFormat":1},{"version":"907c17ad5a05eecb29b42b36cc8fec6437be27cc4986bb3a218e4f74f606911c","impliedFormat":1},{"version":"ce60a562cd2a92f37a88f2ddd99a3abfbc5848d7baf38c48fb8d3243701fcb75","impliedFormat":1},{"version":"a726ad2d0a98bfffbe8bc1cd2d90b6d831638c0adc750ce73103a471eb9a891c","impliedFormat":1},{"version":"f44c0c8ce58d3dacac016607a1a90e5342d830ea84c48d2e571408087ae55894","impliedFormat":1},{"version":"75a315a098e630e734d9bc932d9841b64b30f7a349a20cf4717bf93044eff113","impliedFormat":1},{"version":"9131d95e32b3d4611d4046a613e022637348f6cebfe68230d4e81b691e4761a1","impliedFormat":1},{"version":"b03aa292cfdcd4edc3af00a7dbd71136dd067ec70a7536b655b82f4dd444e857","impliedFormat":1},{"version":"b6e2b0448ced813b8c207810d96551a26e7d7bb73255eea4b9701698f78846d6","impliedFormat":1},{"version":"8ae10cd85c1bd94d2f2d17c4cbd25c068a4b2471c70c2d96434239f97040747a","impliedFormat":1},{"version":"9ed5b799c50467b0c9f81ddf544b6bcda3e34d92076d6cab183c84511e45c39f","impliedFormat":1},{"version":"b4fa87cc1833839e51c49f20de71230e259c15b2c9c3e89e4814acc1d1ef10de","impliedFormat":1},{"version":"e90ac9e4ac0326faa1bc39f37af38ace0f9d4a655cd6d147713c653139cf4928","impliedFormat":1},{"version":"ea27110249d12e072956473a86fd1965df8e1be985f3b686b4e277afefdde584","impliedFormat":1},{"version":"8776a368617ce51129b74db7d55c3373dadcce5d0701e61d106e99998922a239","impliedFormat":1},{"version":"5666075052877fe2fdddd5b16de03168076cf0f03fbca5c1d4a3b8f43cba570c","impliedFormat":1},{"version":"9108ab5af05418f599ab48186193b1b07034c79a4a212a7f73535903ba4ca249","impliedFormat":1},{"version":"bb4e2cdcadf9c9e6ee2820af23cee6582d47c9c9c13b0dca1baaffe01fbbcb5f","impliedFormat":1},{"version":"6e30d0b5a1441d831d19fe02300ab3d83726abd5141cbcc0e2993fa0efd33db4","impliedFormat":1},{"version":"423f28126b2fc8d8d6fa558035309000a1297ed24473c595b7dec52e5c7ebae5","impliedFormat":1},{"version":"fb30734f82083d4790775dae393cd004924ebcbfde49849d9430bf0f0229dd16","impliedFormat":1},{"version":"2c92b04a7a4a1cd9501e1be338bf435738964130fb2ad5bd6c339ee41224ac4c","impliedFormat":1},{"version":"c5c5f0157b41833180419dacfbd2bcce78fb1a51c136bd4bcba5249864d8b9b5","impliedFormat":1},{"version":"02ae43d5bae42efcd5a00d3923e764895ce056bca005a9f4e623aa6b4797c8af","impliedFormat":1},{"version":"db6e01f17012a9d7b610ae764f94a1af850f5d98c9c826ad61747dca0fb800bd","impliedFormat":1},{"version":"8a44b424edee7bb17dc35a558cc15f92555f14a0441205613e0e50452ab3a602","impliedFormat":1},{"version":"24a00d0f98b799e6f628373249ece352b328089c3383b5606214357e9107e7d5","impliedFormat":1},{"version":"33637e3bc64edd2075d4071c55d60b32bdb0d243652977c66c964021b6fc8066","impliedFormat":1},{"version":"0f0ad9f14dedfdca37260931fac1edf0f6b951c629e84027255512f06a6ebc4c","impliedFormat":1},{"version":"16ad86c48bf950f5a480dc812b64225ca4a071827d3d18ffc5ec1ae176399e36","impliedFormat":1},{"version":"8cbf55a11ff59fd2b8e39a4aa08e25c5ddce46e3af0ed71fb51610607a13c505","impliedFormat":1},{"version":"d5bc4544938741f5daf8f3a339bfbf0d880da9e89e79f44a6383aaf056fe0159","impliedFormat":1},{"version":"97f9169882d393e6f303f570168ca86b5fe9aab556e9a43672dae7e6bb8e6495","impliedFormat":1},{"version":"7c9adb3fcd7851497818120b7e151465406e711d6a596a71b807f3a17853cb58","impliedFormat":1},{"version":"6752d402f9282dd6f6317c8c048aaaac27295739a166eed27e00391b358fed9a","impliedFormat":1},{"version":"9fd7466b77020847dbc9d2165829796bf7ea00895b2520ff3752ffdcff53564b","impliedFormat":1},{"version":"fbfc12d54a4488c2eb166ed63bab0fb34413e97069af273210cf39da5280c8d6","impliedFormat":1},{"version":"85a84240002b7cf577cec637167f0383409d086e3c4443852ca248fc6e16711e","impliedFormat":1},{"version":"84794e3abd045880e0fadcf062b648faf982aa80cfc56d28d80120e298178626","impliedFormat":1},{"version":"053d8b827286a16a669a36ffc8ccc8acdf8cc154c096610aa12348b8c493c7b8","impliedFormat":1},{"version":"3cce4ce031710970fe12d4f7834375f5fd455aa129af4c11eb787935923ff551","impliedFormat":1},{"version":"8f62cbd3afbd6a07bb8c934294b6bfbe437021b89e53a4da7de2648ecfc7af25","impliedFormat":1},{"version":"62c3621d34fb2567c17a2c4b89914ebefbfbd1b1b875b070391a7d4f722e55dc","impliedFormat":1},{"version":"c05ac811542e0b59cb9c2e8f60e983461f0b0e39cea93e320fad447ff8e474f3","impliedFormat":1},{"version":"8e7a5b8f867b99cc8763c0b024068fb58e09f7da2c4810c12833e1ca6eb11c4f","impliedFormat":1},{"version":"132351cbd8437a463757d3510258d0fa98fd3ebef336f56d6f359cf3e177a3ce","impliedFormat":1},{"version":"df877050b04c29b9f8409aa10278d586825f511f0841d1ec41b6554f8362092b","impliedFormat":1},{"version":"33d1888c3c27d3180b7fd20bac84e97ecad94b49830d5dd306f9e770213027d1","impliedFormat":1},{"version":"ee942c58036a0de88505ffd7c129f86125b783888288c2389330168677d6347f","impliedFormat":1},{"version":"a3f317d500c30ea56d41501632cdcc376dae6d24770563a5e59c039e1c2a08ec","impliedFormat":1},{"version":"eb21ddc3a8136a12e69176531197def71dc28ffaf357b74d4bf83407bd845991","impliedFormat":1},{"version":"0c1651a159995dfa784c57b4ea9944f16bdf8d924ed2d8b3db5c25d25749a343","impliedFormat":1},{"version":"aaa13958e03409d72e179b5d7f6ec5c6cc666b7be14773ae7b6b5ee4921e52db","impliedFormat":1},{"version":"0a86e049843ad02977a94bb9cdfec287a6c5a0a4b6b5391a6648b1a122072c5a","impliedFormat":1},{"version":"40f06693e2e3e58526b713c937895c02e113552dc8ba81ecd49cdd9596567ddb","impliedFormat":1},{"version":"4ed5e1992aedb174fb8f5aa8796aa6d4dcb8bd819b4af1b162a222b680a37fa0","impliedFormat":1},{"version":"d7f4bd46a8b97232ea6f8c28012b8d2b995e55e729d11405f159d3e00c51420a","impliedFormat":1},{"version":"d604d413aff031f4bfbdae1560e54ebf503d374464d76d50a2c6ded4df525712","impliedFormat":1},{"version":"e4f4f9cf1e3ac9fd91ada072e4d428ecbf0aa6dc57138fb797b8a0ca3a1d521c","impliedFormat":1},{"version":"12bfd290936824373edda13f48a4094adee93239b9a73432db603127881a300d","impliedFormat":1},{"version":"340ceb3ea308f8e98264988a663640e567c553b8d6dc7d5e43a8f3b64f780374","impliedFormat":1},{"version":"c5a769564e530fba3ec696d0a5cff1709b9095a0bdf5b0826d940d2fc9786413","impliedFormat":1},{"version":"7124ef724c3fc833a17896f2d994c368230a8d4b235baed39aa8037db31de54f","impliedFormat":1},{"version":"5de1c0759a76e7710f76899dcae601386424eab11fb2efaf190f2b0f09c3d3d3","impliedFormat":1},{"version":"9c5ee8f7e581f045b6be979f062a61bf076d362bf89c7f966b993a23424e8b0d","impliedFormat":1},{"version":"1a11df987948a86aa1ec4867907c59bdf431f13ed2270444bf47f788a5c7f92d","impliedFormat":1},{"version":"8018dd2e95e7ce6e613ddd81672a54532614dc745520a2f9e3860ff7fb1be0ca","impliedFormat":1},{"version":"b756781cd40d465da57d1fc6a442c34ae61fe8c802d752aace24f6a43fedacee","impliedFormat":1},{"version":"0fe76167c87289ea094e01616dcbab795c11b56bad23e1ef8aba9aa37e93432a","impliedFormat":1},{"version":"3a45029dba46b1f091e8dc4d784e7be970e209cd7d4ff02bd15270a98a9ba24b","impliedFormat":1},{"version":"032c1581f921f8874cf42966f27fd04afcabbb7878fa708a8251cac5415a2a06","impliedFormat":1},{"version":"69c68ed9652842ce4b8e495d63d2cd425862104c9fb7661f72e7aa8a9ef836f8","impliedFormat":1},{"version":"0e704ee6e9fd8b6a5a7167886f4d8915f4bc22ed79f19cb7b32bd28458f50643","impliedFormat":1},{"version":"06f62a14599a68bcde148d1efd60c2e52e8fa540cc7dcfa4477af132bb3de271","impliedFormat":1},{"version":"904a96f84b1bcee9a7f0f258d17f8692e6652a0390566515fe6741a5c6db8c1c","impliedFormat":1},{"version":"11f19ce32d21222419cecab448fa335017ebebf4f9e5457c4fa9df42fa2dcca7","impliedFormat":1},{"version":"2e8ee2cbb5e9159764e2189cf5547aebd0e6b0d9a64d479397bb051cd1991744","impliedFormat":1},{"version":"1b0471d75f5adb7f545c1a97c02a0f825851b95fe6e069ac6ecaa461b8bb321d","impliedFormat":1},{"version":"1d157c31a02b1e5cca9bc495b3d8d39f4b42b409da79f863fb953fbe3c7d4884","impliedFormat":1},{"version":"07baaceaec03d88a4b78cb0651b25f1ae0322ac1aa0b555ae3749a79a41cba86","impliedFormat":1},{"version":"619a132f634b4ebe5b4b4179ea5870f62f2cb09916a25957bff17b408de8b56d","impliedFormat":1},{"version":"f60fa446a397eb1aead9c4e568faf2df8068b4d0306ebc075fb4be16ed26b741","impliedFormat":1},{"version":"f3cb784be4d9e91f966a0b5052a098d9b53b0af0d341f690585b0cc05c6ca412","impliedFormat":1},{"version":"350f63439f8fe2e06c97368ddc7fb6d6c676d54f59520966f7dbbe6a4586014e","impliedFormat":1},{"version":"eba613b9b357ac8c50a925fa31dc7e65ff3b95a07efbaa684b624f143d8d34ba","impliedFormat":1},{"version":"45b74185005ed45bec3f07cac6e4d68eaf02ead9ff5a66721679fb28020e5e7c","impliedFormat":1},{"version":"0f6199602df09bdb12b95b5434f5d7474b1490d2cd8cc036364ab3ba6fd24263","impliedFormat":1},{"version":"c8ca7fd9ec7a3ec82185bfc8213e4a7f63ae748fd6fced931741d23ef4ea3c0f","impliedFormat":1},{"version":"5c6a8a3c2a8d059f0592d4eab59b062210a1c871117968b10797dee36d991ef7","impliedFormat":1},{"version":"ad77fd25ece8e09247040826a777dc181f974d28257c9cd5acb4921b51967bd8","impliedFormat":1},{"version":"795a08ae4e193f345073b49f68826ab6a9b280400b440906e4ec5c237ae777e6","impliedFormat":1},{"version":"8153df63cf65122809db17128e5918f59d6bb43a371b5218f4430c4585f64085","impliedFormat":1},{"version":"a8150bc382dd12ce58e00764d2366e1d59a590288ee3123af8a4a2cb4ef7f9df","impliedFormat":1},{"version":"5adfaf2f9f33957264ad199a186456a4676b2724ed700fc313ff945d03372169","impliedFormat":1},{"version":"d5c41a741cd408c34cb91f84468f70e9bda3dfeabf33251a61039b3cdb8b22d8","impliedFormat":1},{"version":"c91d3f9753a311284e76cdcb348cbb50bca98733336ec726b54d77b7361b34de","impliedFormat":1},{"version":"cbaf4a4aa8a8c02aa681c5870d5c69127974de29b7e01df570edec391a417959","impliedFormat":1},{"version":"c7135e329a18b0e712378d5c7bc2faec6f5ab0e955ea0002250f9e232af8b3e4","impliedFormat":1},{"version":"340a45cd77b41d8a6deda248167fa23d3dc67ec798d411bd282f7b3d555b1695","impliedFormat":1},{"version":"fae330f86bc10db6841b310f32367aaa6f553036a3afc426e0389ddc5566cd74","impliedFormat":1},{"version":"cf25d45c02d5fd5d7adb16230a0e1d6715441eef5c0a79a21bfeaa9bbc058939","impliedFormat":1},{"version":"54c3822eaf6436f2eddc92dd6e410750465aba218adbf8ce5d488d773919ec01","impliedFormat":1},{"version":"99d99a765426accf8133737843fb024a154dc6545fc0ffbba968a7c0b848959d","impliedFormat":1},{"version":"c782c5fd5fa5491c827ecade05c3af3351201dd1c7e77e06711c8029b7a9ee4d","impliedFormat":1},{"version":"883d2104e448bb351c49dd9689a7e8117b480b614b2622732655cef03021bf6d","impliedFormat":1},{"version":"d9b00ee2eca9b149663fdba1c1956331841ae296ee03eaaff6c5becbc0ff1ea8","impliedFormat":1},{"version":"09a7e04beb0547c43270b327c067c85a4e2154372417390731dfe092c4350998","impliedFormat":1},{"version":"eee530aaa93e9ec362e3941ee8355e2d073c7b21d88c2af4713e3d701dab8fef","impliedFormat":1},{"version":"28d47319b97dbeee9130b78eae03b2061d46dedbf92b0d9de13ed7ab8399ccd0","impliedFormat":1},{"version":"8b8b92781a6bf150f9ee83f3d8ee278b6cdb98b8308c7ab3413684fc5d9078ef","impliedFormat":1},{"version":"7a0e4cd92545ad03910fd019ae9838718643bd4dde39881c745f236914901dfa","impliedFormat":1},{"version":"c99ebd20316217e349004ee1a0bc74d32d041fb6864093f10f31984c737b8cad","impliedFormat":1},{"version":"6f622e7f054f5ab86258362ac0a64a2d6a27f1e88732d6f5f052f422e08a70e7","impliedFormat":1},{"version":"d62d2ef93ceeb41cf9dfab25989a1e5f9ca5160741aac7f1453c69a6c14c69be","impliedFormat":1},{"version":"1491e80d72873fc586605283f2d9056ee59b166333a769e64378240df130d1c9","impliedFormat":1},{"version":"c32c073d389cfaa3b3e562423e16c2e6d26b8edebbb7d73ccffff4aa66f2171d","impliedFormat":1},{"version":"eca72bf229eecadb63e758613c62fab13815879053539a22477d83a48a21cd73","impliedFormat":1},{"version":"633db46fd1765736409a4767bfc670861468dde60dbb9a501fba4c1b72f8644d","impliedFormat":1},{"version":"689390db63cb282e6d0e5ce9b8f1ec2ec0912d0e2e6dac7235699a15ad17d339","impliedFormat":1},{"version":"f2ee748883723aa9325e5d7f30fce424f6a786706e1b91a5a55237c78ee89c4a","impliedFormat":1},{"version":"d928324d17146fce30b99a28d1d6b48648feac72bbd23641d3ce5ac34aefdfee","impliedFormat":1},{"version":"142f5190d730259339be1433931c0eb31ae7c7806f4e325f8a470bd9221b6533","impliedFormat":1},{"version":"c33a88f2578e8df2fdf36c6a0482bbee615eb3234c8f084ba31a9a96bd306b7f","impliedFormat":1},{"version":"22cca068109eb0e6b4f8acc3fe638d1e6ac277e2044246438763319792b546a1","impliedFormat":1},{"version":"8776e64e6165838ac152fa949456732755b0976d1867ae5534ce248f0ccd7f41","impliedFormat":1},{"version":"66cd33c4151ea27f6e17c6071652eadde9da1b3637dae65fd060212211c695ce","impliedFormat":1},{"version":"5c4c5b49bbb01828402bb04af1d71673b18852c11b7e95bfd5cf4c3d80d352c8","impliedFormat":1},{"version":"7030df3d920343df00324df59dc93a959a33e0f4940af3fefef8c07b7ee329bf","impliedFormat":1},{"version":"a96bc00e0c356e29e620eaec24a56d6dd7f4e304feefcc99066a1141c6fe05a7","impliedFormat":1},{"version":"d12cc0e5b09943c4cd0848f787eb9d07bf78b60798e4588c50582db9d4decc70","impliedFormat":1},{"version":"53b094f1afe442490555eeeb0384fc1ceb487560c83e31f9c64fb934c2dccd94","impliedFormat":1},{"version":"19c3760af3cbc9da99d5b7763b9e33aaf8d018bc2ed843287b7ff4343adf4634","impliedFormat":1},{"version":"9d1e38aeb76084848d2fcd39b458ec88246de028c0f3f448b304b15d764b23d2","impliedFormat":1},{"version":"d406da1eccf18cec56fd29730c24af69758fe3ff49c4f94335e797119cbc0554","impliedFormat":1},{"version":"4898c93890a136da9156c75acd1a80a941a961b3032a0cf14e1fa09a764448b7","impliedFormat":1},{"version":"f5d7a845e3e1c6c27351ea5f358073d0b0681537a2da6201fab254aa434121d3","impliedFormat":1},{"version":"9ddf8e9069327faa75d20135cab675779844f66590249769c3d35dd2a38c2ba9","impliedFormat":1},{"version":"d7c30f0abfe9e197e376b016086cf66b2ffb84015139963f37301ed0da9d3d0d","impliedFormat":1},{"version":"ff75bba0148f07775bcb54bf4823421ed4ebdb751b3bf79cc003bd22e49d7d73","impliedFormat":1},{"version":"d40d20ac633703a7333770bfd60360126fc3302d5392d237bbb76e8c529a4f95","impliedFormat":1},{"version":"35a9867207c488061fb4f6fe4715802fbc164b4400018d2fa0149ad02db9a61c","impliedFormat":1},{"version":"91bf47a209ad0eae090023c3ebc1165a491cf9758799368ffcbee8dbe7448f33","impliedFormat":1},{"version":"0abe2cd72812bbfc509975860277c7cd6f6e0be95d765a9da77fee98264a7e32","impliedFormat":1},{"version":"13286c0c8524606b17a8d68650970bab896fb505f348f71601abf0f2296e8913","impliedFormat":1},{"version":"fc2a131847515b3dff2f0e835633d9a00a9d03ed59e690e27eec85b7b0522f92","impliedFormat":1},{"version":"90433c678bc26751eb7a5d54a2bb0a14be6f5717f69abb5f7a04afc75dce15a4","impliedFormat":1},{"version":"cd0565ace87a2d7802bf4c20ea23a997c54e598b9eb89f9c75e69478c1f7a0b4","impliedFormat":1},{"version":"738020d2c8fc9df92d5dee4b682d35a776eaedfe2166d12bc8f186e1ea57cc52","impliedFormat":1},{"version":"86dd7c5657a0b0bc6bee8002edcfd544458d3d3c60974555746eb9b2583dc35e","impliedFormat":1},{"version":"d97b96b6ecd4ee03f9f1170722c825ef778430a6a0d7aab03b8929012bf773cd","impliedFormat":1},{"version":"f61963dc02ef27c48fb0e0016a413b1e00bcb8b97a3f5d4473cedc7b44c8dc77","impliedFormat":1},{"version":"272dbfe04cfa965d6fff63fdaba415c1b5a515b1881ae265148f8a84ddeb318f","impliedFormat":1},{"version":"2035fb009b5fafa9a4f4e3b3fdb06d9225b89f2cbbf17a5b62413bf72cea721a","impliedFormat":1},{"version":"eefafec7c059f07b885b79b327d381c9a560e82b439793de597441a4e68d774a","impliedFormat":1},{"version":"72636f59b635c378dc9ea5246b9b3517b1214e340e468e54cb80126353053b2e","impliedFormat":1},{"version":"ebb79f267a3bf2de5f8edc1995c5d31777b539935fab8b7d863e8efb06c8e9ea","impliedFormat":1},{"version":"ada033e6a4c7f4e147e6d76bb881069dc66750619f8cc2472d65beeec1100145","impliedFormat":1},{"version":"0c04cc14a807a5dc0e3752d18a3b2655a135fefbf76ddcdabd0c5df037530d41","impliedFormat":1},{"version":"605d29d619180fbec287d1701e8b1f51f2d16747ec308d20aba3e9a0dac43a0f","impliedFormat":1},{"version":"67c19848b442d77c767414084fc571ce118b08301c4ddff904889d318f3a3363","impliedFormat":1},{"version":"c704ff0e0cb86d1b791767a88af21dadfee259180720a14c12baee668d0eb8fb","impliedFormat":1},{"version":"195c50e15d5b3ea034e01fbdca6f8ad4b35ad47463805bb0360bdffd6fce3009","impliedFormat":1},{"version":"da665f00b6877ae4adb39cd548257f487a76e3d99e006a702a4f38b4b39431cb","impliedFormat":1},{"version":"2b82adc9eead34b824a3f4dad315203fbfa56bee0061ccf9b485820606564f70","impliedFormat":1},{"version":"eb47aaa5e1b0a69388bb48422a991b9364a9c206a97983e0227289a9e1fca178","impliedFormat":1},{"version":"d7a4309673b06223537bc9544b1a5fe9425628e1c8ab5605f3c5ebc27ecb8074","impliedFormat":1},{"version":"db2108aea36e7faa83c38f6fe8225b9ad40835c0cba7fa38e969768299b83173","impliedFormat":1},{"version":"3eadfd083d40777b403f4f4eecfa40f93876f2a01779157cc114b2565a7afb51","impliedFormat":1},{"version":"cb6789ce3eba018d5a7996ccbf50e27541d850e9b4ee97fdcb3cbd8c5093691f","impliedFormat":1},{"version":"a3684ea9719122f9477902acd08cd363a6f3cff6d493df89d4dc12fa58204e27","impliedFormat":1},{"version":"2828dabf17a6507d39ebcc58fef847e111dcf2d51b8e4ff0d32732c72be032b3","impliedFormat":1},{"version":"c0c46113b4cd5ec9e7cf56e6dbfb3930ef6cbba914c0883eeced396988ae8320","impliedFormat":1},{"version":"118ea3f4e7b9c12e92551be0766706f57a411b4f18a1b4762cfde3cd6d4f0a96","impliedFormat":1},{"version":"2ad163aaddfa29231a021de6838f59378a210501634f125ed04cfa7d066ffc53","impliedFormat":1},{"version":"6305acbe492b9882ec940f8f0c8e5d1e1395258852f99328efcb1cf1683ca817","impliedFormat":1},{"version":"7619b1f6087a4e9336b2c42bd784b05aa4a2204a364b60171e5a628f817a381e","impliedFormat":1},{"version":"15be9120572c9fbcd3c267bd93b4140354514c9e70734e6fcca65ff4a246f83a","impliedFormat":1},{"version":"412482ab85893cec1d6f26231359474d1f59f6339e2743c08da1b05fc1d12767","impliedFormat":1},{"version":"858e2315e58af0d28fcd7f141a2505aba6a76fd10378ba0ad169b0336fee33fc","impliedFormat":1},{"version":"02da6c1b34f4ae2120d70cf5f9268bf1aedf62e55529d34f5974f5a93655ce38","impliedFormat":1},{"version":"3ecf179ef1cc28f7f9b46c8d2e496d50b542c176e94ed0147bab147b4a961cd6","impliedFormat":1},{"version":"b145da03ce7e174af5ced2cbbd16e96d3d5c2212f9a90d3657b63a5650a73b7f","impliedFormat":1},{"version":"c7aadab66a2bc90eeb0ab145ca4daebcbc038e24359263de3b40e7b1c7affba6","impliedFormat":1},{"version":"99518dc06286877a7b716e0f22c1a72d3c62be42701324b49f27bcc03573efff","impliedFormat":1},{"version":"f4575fd196a7e33c7be9773a71bcc5fbe7182a2152be909f6b8e8e7ba2438f06","impliedFormat":1},{"version":"05cba5acd77a4384389b9c62739104b5a1693efd66e6abac6c5ffc53280ae777","impliedFormat":1},{"version":"acacda82ebd929fe2fe9e31a37f193fc8498a7393a1c31dc5ceb656e2b45b708","impliedFormat":1},{"version":"1b13e7c5c58ab894fe65b099b6d19bb8afae6d04252db1bf55fe6ba95a0af954","impliedFormat":1},{"version":"4355d326c3129e5853b56267903f294ad03e34cc28b75f96b80734882dedac80","impliedFormat":1},{"version":"37139a8d45342c05b6a5aa1698a2e8e882d6dca5fb9a77aa91f05ac04e92e70b","impliedFormat":1},{"version":"e37191297f1234d3ae54edbf174489f9a3091a05fe959724db36f8e58d21fb17","impliedFormat":1},{"version":"3fca8fb3aab1bc7abb9b1420f517e9012fdddcbe18803bea2dd48fad6c45e92e","impliedFormat":1},{"version":"d0b0779e0cac4809a9a3c764ba3bd68314de758765a8e3b9291fe1671bfeb8a1","impliedFormat":1},{"version":"d2116b5f989aa68e585ae261b9d6d836be6ed1be0b55b47336d9f3db34674e86","impliedFormat":1},{"version":"d79a227dd654be16d8006eac8b67212679d1df494dfe6da22ea0bd34a13e010c","impliedFormat":1},{"version":"b9c89b4a2435c171e0a9a56668f510a376cb7991eaecef08b619e6d484841735","impliedFormat":1},{"version":"44a298a6c52a7dab8e970e95a6dabe20972a7c31c340842e0dc57f2c822826eb","impliedFormat":1},{"version":"6a79b61f57699de0a381c8a13f4c4bcd120556bfab0b4576994b6917cb62948b","impliedFormat":1},{"version":"c5133d7bdec65f465df12f0b507fbc0d96c78bfa5a012b0eb322cf1ff654e733","impliedFormat":1},{"version":"00b9ff040025f6b00e0f4ac8305fea1809975b325af31541bd9d69fa3b5e57b1","impliedFormat":1},{"version":"9f96b9fd0362a7bfe6a3aa70baa883c47ae167469c904782c99ccc942f62f0dc","impliedFormat":1},{"version":"54d91053dc6a2936bfd01a130cc3b524e11aa0349da082e8ac03a8bf44250338","impliedFormat":1},{"version":"89049878a456b5e0870bb50289ea8ece28a2abd0255301a261fa8ab6a3e9a07d","impliedFormat":1},{"version":"55ae9554811525f24818e19bdc8779fa99df434be7c03e5fc47fa441315f0226","impliedFormat":1},{"version":"24abac81e9c60089a126704e936192b2309413b40a53d9da68dadd1dd107684e","impliedFormat":1},{"version":"f13310c360ecffddb3858dcb33a7619665369d465f55e7386c31d45dfc3847bf","impliedFormat":1},{"version":"e7bde95a05a0564ee1450bc9a53797b0ac7944bf24d87d6f645baca3aa60df48","impliedFormat":1},{"version":"62e68ce120914431a7d34232d3eca643a7ddd67584387936a5202ae1c4dd9a1b","impliedFormat":1},{"version":"91d695bba902cc2eda7edc076cd17c5c9340f7bb254597deb6679e343effadbb","impliedFormat":1},{"version":"e1cb8168c7e0bd4857a66558fe7fe6c66d08432a0a943c51bacdac83773d5745","impliedFormat":1},{"version":"a464510505f31a356e9833963d89ce39f37a098715fc2863e533255af4410525","impliedFormat":1},{"version":"ebbe6765a836bfa7f03181bc433c8984ca29626270ca1e240c009851222cb8a7","impliedFormat":1},{"version":"ac10457b51ee4a3173b7165c87c795eadd094e024f1d9f0b6f0c131126e3d903","impliedFormat":1},{"version":"468df9d24a6e2bc6b4351417e3b5b4c2ca08264d6d5045fe18eb42e7996e58b4","impliedFormat":1},{"version":"954523d1f4856180cbf79b35bd754e14d3b2aea06c7efd71b254c745976086e9","impliedFormat":1},{"version":"a8af4739274959d70f7da4bfdd64f71cfc08d825c2d5d3561bc7baed760b33ef","impliedFormat":1},{"version":"090fda1107e7d4f8f30a2b341834ed949f01737b5ec6021bb6981f8907330bdb","impliedFormat":1},{"version":"cc32874a27100c32e3706d347eb4f435d6dd5c0d83e547c157352f977bbc6385","impliedFormat":1},{"version":"e45b069d58c9ac341d371b8bc3db4fa7351b9eee1731bffd651cfc1eb622f844","impliedFormat":1},{"version":"7f3c74caad25bfb6dfbf78c6fe194efcf8f79d1703d785fc05cd606fe0270525","impliedFormat":1},{"version":"54f3f7ff36384ca5c9e1627118b43df3014b7e0f62c9722619d19cdb7e43d608","impliedFormat":1},{"version":"2f346f1233bae487f1f9a11025fc73a1bf9093ee47980a9f4a75b84ea0bb7021","impliedFormat":1},{"version":"013444d0b8c1f7b5115462c31573a699fee7458381b0611062a0069d3ef810e8","impliedFormat":1},{"version":"0612b149cabbc136cb25de9daf062659f306b67793edc5e39755c51c724e2949","impliedFormat":1},{"version":"2579b150b86b5f644d86a6d58f17e3b801772c78866c34d41f86f3fc9eb523fe","impliedFormat":1},{"version":"0353e05b0d8475c10ddd88056e0483b191aa5cdea00a25e0505b96e023f1a2d9","impliedFormat":1},{"version":"8c4df93dafcf06adc42a63477cc38b352565a3ed0a19dd8ef7dfacc253749327","impliedFormat":1},{"version":"22a35275abc67f8aba44efc52b2f4b1abc2c94e183d36647fdab5a5e7c1bdf23","impliedFormat":1},{"version":"99193bafaa9ce112889698de25c4b8c80b1209bb7402189aea1c7ada708a8a54","impliedFormat":1},{"version":"70473538c6eb9494d53bf1539fe69df68d87c348743d8f7244dcb02ca3619484","impliedFormat":1},{"version":"c48932ab06a4e7531bdca7b0f739ace5fa273f9a1b9009bcd26902f8c0b851f0","impliedFormat":1},{"version":"df6c83e574308f6540c19e3409370482a7d8f448d56c65790b4ac0ab6f6fedd8","impliedFormat":1},{"version":"32f19b665839b1382b21afc41917cda47a56e744cd3df9986b13a72746d1c522","impliedFormat":1},{"version":"8db1ed144dd2304b9bd6e41211e22bad5f4ab1d8006e6ac127b29599f4b36083","impliedFormat":1},{"version":"843a5e3737f2abbbbd43bf2014b70f1c69a80530814a27ae1f8be213ae9ec222","impliedFormat":1},{"version":"6fc1be224ad6b3f3ec11535820def2d21636a47205c2c9de32238ba1ac8d82e6","impliedFormat":1},{"version":"5a44788293f9165116c9c183be66cefef0dc5d718782a04847de53bf664f3cc1","impliedFormat":1},{"version":"afd653ae63ce07075b018ba5ce8f4e977b6055c81cc65998410b904b94003c0a","impliedFormat":1},{"version":"9172155acfeb17b9d75f65b84f36cb3eb0ff3cd763db3f0d1ad5f6d10d55662f","impliedFormat":1},{"version":"71807b208e5f15feffb3ff530bec5b46b1217af0d8cc96dde00d549353bcb864","impliedFormat":1},{"version":"1a6eca5c2bc446481046c01a54553c3ffb856f81607a074f9f0256c59dd0ab13","impliedFormat":1},{"version":"b8ad793dc17938bc462812e3522bbd3d62519d91d9b4a6422bed1383c2d3eb42","impliedFormat":1},{"version":"8b0b6a4c032a56d5651f7dd02ba3f05fbfe4131c4095093633cda3cae0991972","impliedFormat":1},{"version":"ff3c48a17bf10dfbb62448152042e4a48a56c9972059997ab9e7ed03b191809b","impliedFormat":1},{"version":"192a0c215bffe5e4ac7b9ff1e90e94bf4dfdad4f0f69a5ae07fccc36435ebb87","impliedFormat":1},{"version":"3ef8565e3d254583cced37534f161c31e3a8f341ff005c98b582c6d8c9274538","impliedFormat":1},{"version":"d7e42a3800e287d2a1af8479c7dd58c8663e80a01686cb89e0068be6c777d687","impliedFormat":1},{"version":"1098034333d3eb3c1d974435cacba9bd5a625711453412b3a514774fec7ca748","impliedFormat":1},{"version":"f2388b97b898a93d5a864e85627e3af8638695ebfa6d732ecd39d382824f0e63","impliedFormat":1},{"version":"6c6bd91368169cfa94b4f8cc64ebca2b050685ec76bc4082c44ce125b5530cca","impliedFormat":1},{"version":"f477375e6f0bf2a638a71d4e7a3da8885e3a03f3e5350688541d136b10b762a6","impliedFormat":1},{"version":"a44d6ea4dc70c3d789e9cef3cc42b79c78d17d3ce07f5fd278a7e1cbe824da56","impliedFormat":1},{"version":"272af80940fcc0c8325e4a04322c50d11f8b8842f96ac66cbd440835e958dd14","impliedFormat":1},{"version":"1803e48a3ec919ccafbcafeef5e410776ca0644ae8c6c87beca4c92d8a964434","impliedFormat":1},{"version":"875c43c5409e197e72ee517cb1f8fd358406b4adf058dbdc1e50c8db93d68f26","impliedFormat":1},{"version":"8854713984b9588eac1cab69c9e2a6e1a33760d9a2d182169059991914dd8577","impliedFormat":1},{"version":"e333d487ca89f26eafb95ea4b59bea8ba26b357e9f2fd3728be81d999f9e8cf6","impliedFormat":1},{"version":"2f554c6798b731fc39ff4e3d86aadc932fdeaa063e3cbab025623ff5653c0031","impliedFormat":1},{"version":"fe4613c6c0d23edc04cd8585bdd86bc7337dc6265fb52037d11ca19eeb5e5aaf","impliedFormat":1},{"version":"53b26fbee1a21a6403cf4625d0e501a966b9ccf735754b854366cee8984b711c","impliedFormat":1},{"version":"c503be3ddb3990ab27ca20c6559d29b547d9f9413e05d2987dd7c4bcf52f3736","impliedFormat":1},{"version":"598b15f0ae9a73082631d14cb8297a1285150ca325dbce98fc29c4f0b7079443","impliedFormat":1},{"version":"8c59d8256086ed17676139ee43c1155673e357ab956fb9d00711a7cac73e059d","impliedFormat":1},{"version":"cfe88132f67aa055a3f49d59b01585fa8d890f5a66a0a13bb71973d57573eee7","impliedFormat":1},{"version":"53ce488a97f0b50686ade64252f60a1e491591dd7324f017b86d78239bd232ca","impliedFormat":1},{"version":"50fd11b764194f06977c162c37e5a70bcf0d3579bf82dd4de4eee3ac68d0f82f","impliedFormat":1},{"version":"e0ceb647dcdf6b27fd37e8b0406c7eafb8adfc99414837f3c9bfd28ffed6150a","impliedFormat":1},{"version":"99579aa074ed298e7a3d6a47e68f0cd099e92411212d5081ce88344a5b1b528d","impliedFormat":1},{"version":"c94c1aa80687a277396307b80774ca540d0559c2f7ba340168c2637c82b1f766","impliedFormat":1},{"version":"ce7dbf31739cc7bca35ca50e4f0cbd75cd31fd6c05c66841f8748e225dc73aaf","impliedFormat":1},{"version":"942ab34f62ac3f3d20014615b6442b6dc51815e30a878ebc390dd70e0dec63bf","impliedFormat":1},{"version":"7a671bf8b4ad81b8b8aea76213ca31b8a5de4ba39490fbdee249fc5ba974a622","impliedFormat":1},{"version":"8e07f13fb0f67e12863b096734f004e14c5ebfd34a524ed4c863c80354c25a44","impliedFormat":1},{"version":"6f6bdb523e5162216efc36ebba4f1ef8e845f1a9e55f15387df8e85206448aee","impliedFormat":1},{"version":"aa2d6531a04d6379318d29891de396f61ccc171bfd2f8448cc1649c184becdf2","impliedFormat":1},{"version":"d422f0c340060a53cb56d0db24dd170e31e236a808130ab106f7ab2c846f1cdb","impliedFormat":1},{"version":"424403ef35c4c97a7f00ea85f4a5e2f088659c731e75dbe0c546137cb64ef8d8","impliedFormat":1},{"version":"16900e9a60518461d7889be8efeca3fe2cbcd3f6ce6dee70fea81dfbf8990a76","impliedFormat":1},{"version":"6daf17b3bd9499bd0cc1733ab227267d48cd0145ed9967c983ccb8f52eb72d6e","impliedFormat":1},{"version":"e4177e6220d0fef2500432c723dbd2eb9a27dcb491344e6b342be58cc1379ec0","impliedFormat":1},{"version":"ab710f1ee2866e473454a348cffd8d5486e3c07c255f214e19e59a4f17eece4d","impliedFormat":1},{"version":"db7ff3459e80382c61441ea9171f183252b6acc82957ecb6285fff4dca55c585","impliedFormat":1},{"version":"4a168e11fe0f46918721d2f6fcdb676333395736371db1c113ae30b6fde9ccd2","impliedFormat":1},{"version":"2a899aef0c6c94cc3537fe93ec8047647e77a3f52ee7cacda95a8c956d3623fb","impliedFormat":1},{"version":"ef2c1585cad462bdf65f2640e7bcd75cd0dbc45bae297e75072e11fe3db017fa","impliedFormat":1},{"version":"6a52170a5e4600bbb47a94a1dd9522dca7348ce591d8cdbb7d4fe3e23bbea461","impliedFormat":1},{"version":"6f6eadb32844b0ec7b322293b011316486894f110443197c4c9fbcba01b3b2fa","impliedFormat":1},{"version":"a51e08f41e3e948c287268a275bfe652856a10f68ddd2bf3e3aaf5b8cdb9ef85","impliedFormat":1},{"version":"16c144a21cd99926eeba1605aec9984439e91aa864d1c210e176ca668f5f586a","impliedFormat":1},{"version":"af48a76b75041e2b3e7bd8eed786c07f39ea896bb2ff165e27e18208d09b8bee","impliedFormat":1},{"version":"fd4107bd5c899165a21ab93768904d5cfb3e98b952f91fbf5a12789a4c0744e6","impliedFormat":1},{"version":"deb092bc337b2cb0a1b14f3d43f56bc663e1447694e6d479d6df8296bdd452d6","impliedFormat":1},{"version":"041bc1c3620322cb6152183857601707ef6626e9d99f736e8780533689fb1bf9","impliedFormat":1},{"version":"77165b117f552be305d3bc2ef83424ff1e67afb22bfabd14ebebb3468c21fcaa","impliedFormat":1},{"version":"128e7c2ffd37aa29e05367400d718b0e4770cefb1e658d8783ec80a16bc0643a","impliedFormat":1},{"version":"076ac4f2d642c473fa7f01c8c1b7b4ef58f921130174d9cf78430651f44c43ec","impliedFormat":1},{"version":"396c1e5a39706999ec8cc582916e05fcb4f901631d2c192c1292e95089a494d9","impliedFormat":1},{"version":"89df75d28f34fc698fe261f9489125b4e5828fbd62d863bbe93373d3ed995056","impliedFormat":1},{"version":"8ccf5843249a042f4553a308816fe8a03aa423e55544637757d0cfa338bb5186","impliedFormat":1},{"version":"93b44aa4a7b27ba57d9e2bad6fb7943956de85c5cc330d2c3e30cd25b4583d44","impliedFormat":1},{"version":"a0c6216075f54cafdfa90412596b165ff85e2cadd319c49557cc8410f487b77c","impliedFormat":1},{"version":"3c359d811ec0097cba00fb2afd844b125a2ddf4cad88afaf864e88c8d3d358bd","impliedFormat":1},{"version":"d8ec19be7d6d3950992c3418f3a4aa2bcad144252bd7c0891462b5879f436e4e","impliedFormat":1},{"version":"db37aa3208b48bdcbc27c0c1ae3d1b86c0d5159e65543e8ab79cbfb37b1f2f34","impliedFormat":1},{"version":"d62f09256941e92a95b78ae2267e4cf5ff2ca8915d62b9561b1bc85af1baf428","impliedFormat":1},{"version":"e6223b7263dd7a49f4691bf8df2b1e69f764fb46972937e6f9b28538d050b1ba","impliedFormat":1},{"version":"2daf06d8e15cbca27baa6c106253b92dad96afd87af9996cf49a47103b97dc95","impliedFormat":1},{"version":"1db014db736a09668e0c0576585174dbcfd6471bb5e2d79f151a241e0d18d66b","impliedFormat":1},{"version":"8a153d30edde9cefd102e5523b5a9673c298fc7cf7af5173ae946cbb8dd48f11","impliedFormat":1},{"version":"abaaf8d606990f505ee5f76d0b45a44df60886a7d470820fcfb2c06eafa99659","impliedFormat":1},{"version":"8109e0580fc71dbefd6091b8825acf83209b6c07d3f54c33afeafab5e1f88844","impliedFormat":1},{"version":"d92a80c2c05cf974704088f9da904fe5eadc0b3ad49ddd1ef70ca8028b5adda1","impliedFormat":1},{"version":"fbd7450f20b4486c54f8a90486c395b14f76da66ba30a7d83590e199848f0660","impliedFormat":1},{"version":"ece5b0e45c865645ab65880854899a5422a0b76ada7baa49300c76d38a530ee1","impliedFormat":1},{"version":"62d89ac385aeab821e2d55b4f9a23a277d44f33c67fefe4859c17b80fdb397ea","impliedFormat":1},{"version":"f4dee11887c5564886026263c6ee65c0babc971b2b8848d85c35927af25da827","impliedFormat":1},{"version":"fb8dd49a4cd6d802be4554fbab193bb06e2035905779777f32326cb57cf6a2c2","impliedFormat":1},{"version":"df29ade4994de2d9327a5f44a706bbe6103022a8f40316839afa38d3e078ee06","impliedFormat":1},{"version":"82d3e00d56a71fc169f3cf9ec5f5ffcc92f6c0e67d4dfc130dafe9f1886d5515","impliedFormat":1},{"version":"d38f45cb868a830d130ac8b87d3f7e8caff4961a3a1feae055de5e538e20879a","impliedFormat":1},{"version":"4c30a5cb3097befb9704d16aa4670e64e39ea69c5964a1433b9ffd32e1a5a3a1","impliedFormat":1},{"version":"1b33478647aa1b771314745807397002a410c746480e9447db959110999873ce","impliedFormat":1},{"version":"7b3a5e25bf3c51af55cb2986b89949317aa0f6cbfb5317edd7d4037fa52219a9","impliedFormat":1},{"version":"3cd50f6a83629c0ec330fc482e587bfa96532d4c9ce85e6c3ddf9f52f63eee11","impliedFormat":1},{"version":"9fac6ebf3c60ced53dd21def30a679ec225fc3ff4b8d66b86326c285a4eebb5a","impliedFormat":1},{"version":"8cb83cb98c460cd716d2a98b64eb1a07a3a65c7362436550e02f5c2d212871d1","impliedFormat":1},{"version":"07bc8a3551e39e70c38e7293b1a09916867d728043e352b119f951742cb91624","impliedFormat":1},{"version":"e47adc2176f43c617c0ab47f2d9b2bb1706d9e0669bf349a30c3fe09ddd63261","impliedFormat":1},{"version":"7fec79dfd7319fec7456b1b53134edb54c411ba493a0aef350eee75a4f223eeb","impliedFormat":1},{"version":"189c489705bb96a308dcde9b3336011d08bfbca568bcaf5d5d55c05468e9de7a","impliedFormat":1},{"version":"98f4b1074567341764b580bf14c5aabe82a4390d11553780814f7e932970a6f7","impliedFormat":1},{"version":"dadfa5fd3d5c511ca6bfe240243b5cf2e0f87e44ea63e23c4b2fce253c0d4601","impliedFormat":1},{"version":"2e252235037a2cd8feebfbf74aa460f783e5d423895d13f29a934d7655a1f8be","impliedFormat":1},{"version":"763f4ac187891a6d71ae8821f45eef7ff915b5d687233349e2c8a76c22b3bf2a","impliedFormat":1},{"version":"2e19656c513ded3efe9d292e55d3661b47f21f48f9c7b22003b8522d6d78e42f","impliedFormat":1},{"version":"ddecf238214bfa352f7fb8ed748a7ec6c80f1edcb45053af466a4aa6a2b85ffe","impliedFormat":1},{"version":"896eec3b830d89bc3fb20a38589c111bbe4183dd422e61c6c985d6ccec46a1e9","impliedFormat":1},{"version":"907dab3492fc59404ecf40f9ad655251741c5f2e471bb0376d11dae3e27cb1d8","impliedFormat":1},{"version":"8629340be5692664c52a0e242705616c92b21330cb20acf23425fff401ac771f","impliedFormat":1},{"version":"81477bb2c9b97a9dd5ce7750ab4ae655e74172f0d536d637be345ba76b41cd92","impliedFormat":1},{"version":"55a6b0318ec658ff37bc88e18a93e5f10ddad7257b379b71abf39e6868b8d4d2","impliedFormat":1},{"version":"b7d85dc2de8db4ca983d848c8cfad6cf4d743f8cb35afe1957bedf997c858052","impliedFormat":1},{"version":"83daad5d7ae60a0aede88ea6b9e40853abcbe279c10187342b25e96e35bc9f78","impliedFormat":1},{"version":"3a4e276e678bae861d453944cf92178deaf9b6dcd363c8d10d5dd89d81b74a0c","impliedFormat":1},{"version":"db9661c9bca73e5be82c90359e6217540fd3fd674f0b9403edf04a619a57d563","impliedFormat":1},{"version":"f7a5ab7b54bdc6a13cf1015e1b5d6eeb31d765d54045281bfeefcdfcc982a37c","impliedFormat":1},{"version":"ec99a3d23510a4cb5bdc996b9f2170c78cde2bfa89a5aee4ca2c009a5f122310","impliedFormat":1},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"13f5c844119c43e51ce777c509267f14d6aaf31eafb2c2b002ca35584cd13b29","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"9855e02d837744303391e5623a531734443a5f8e6e8755e018c41d63ad797db2","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"c906fb15bd2aabc9ed1e3f44eb6a8661199d6c320b3aa196b826121552cb3695","impliedFormat":1},{"version":"22295e8103f1d6d8ea4b5d6211e43421fe4564e34d0dd8e09e520e452d89e659","impliedFormat":1},{"version":"58647d85d0f722a1ce9de50955df60a7489f0593bf1a7015521efe901c06d770","impliedFormat":1},{"version":"6b4e081d55ac24fc8a4631d5dd77fe249fa25900abd7d046abb87d90e3b45645","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"814118df420c4e38fe5ae1b9a3bafb6e9c2aa40838e528cde908381867be6466","impliedFormat":1},{"version":"a3fc63c0d7b031693f665f5494412ba4b551fe644ededccc0ab5922401079c95","impliedFormat":1},{"version":"80523c00b8544a2000ae0143e4a90a00b47f99823eb7926c1e03c494216fc363","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"746911b62b329587939560deb5c036aca48aece03147b021fa680223255d5183","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"c8d3e5a18ba35629954e48c4cc8f11dc88224650067a172685c736b27a34a4dc","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2b55d426ff2b9087485e52ac4bc7cfafe1dc420fc76dad926cd46526567c501a","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"47613031a5a31510831304405af561b0ffaedb734437c595256bb61a90f9311b","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"8a1a0d0a4a06a8d278947fcb66bf684f117bf147f89b06e50662d79a53be3e9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"9f55299850d4f0921e79b6bf344b47c420ce0f507b9dcf593e532b09ea7eeea1","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"f4db16820c99b6db923ab18af5fecb02331d785c4c2a8a88373a0cfc08256589","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"90407bbaa24977b8a6a90861148ac98d8652afe69992a90d823f29e9807fe2d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"6fd11f7d83a23fa7e933226132d2a78941e00cf65a89ccac736dfb07a16e8ea6","impliedFormat":1},{"version":"746c149d3bab9cd1ccf4f7a1ebccb387632821beb265a2b673b518e4c35c25fa","impliedFormat":1},{"version":"d5eb5865d4cbaa9985cc3cfb920b230cdcf3363f1e70903a08dc4baab80b0ce1","impliedFormat":1},{"version":"51ebca098538b252953b1ef83c165f25b52271bfb6049cd09d197dddd4cd43c5","impliedFormat":1},"ea66c08e0e2b22c12715fa6dfbf1d316993da3cdeb169126e466f4f29b0ef018","acf31f8c16a0bf8a333161118b5fcb6b56c3d7b2606d0922467bdd543115914a","67d2c26dd0b30f371d271ee05b43630d5f29f525c06d4319bbee1eb99cbb11b9","d58862248cbe0ae5caad80d5110a47f6fc1099589d0f817ba2248630396c990f","9223a0889abb0669020e94a9b8c1e68274cdc05533c1f79d84fe516450e94ebd","b417a8ce939b426a8c4cdb3e2f604515b8696bdafbd25155b1d390cb8d21db16",{"version":"cb5eaaa2a079305b1c5344af739b29c479746f7a7aefffc7175d23d8b7c8dbb0","impliedFormat":1},{"version":"bd324dccada40f2c94aaa1ebc82b11ce3927b7a2fe74a5ab92b431d495a86e6f","impliedFormat":1},{"version":"56749bf8b557c4c76181b2fd87e41bde2b67843303ae2eabb299623897d704d6","impliedFormat":1},{"version":"5a6fbec8c8e62c37e9685a91a6ef0f6ecaddb1ee90f7b2c2b71b454b40a0d9a6","impliedFormat":1},{"version":"e7435f2f56c50688250f3b6ef99d8f3a1443f4e3d65b4526dfb31dfd4ba532f8","impliedFormat":1},{"version":"51a674644ed5a181ec1497c1b4aa6f8cfcbe3af5481bdb641c1b5a498bcb6f3c","impliedFormat":1},{"version":"33b7f4106cf45ae7ccbb95acd551e9a5cd3c27f598d48216bda84213b8ae0c7e","impliedFormat":1},{"version":"176d6f604b228f727afb8e96fd6ff78c7ca38102e07acfb86a0034d8f8a2064a","impliedFormat":1},{"version":"1b1a02c54361b8c222392054648a2137fc5983ad5680134a653b1d9f655fe43d","impliedFormat":1},{"version":"8bcb884d06860a129dbffa3500d51116d9d1040bb3bf1c9762eb2f1e7fd5c85c","impliedFormat":1},{"version":"e55c0f31407e1e4eee10994001a4f570e1817897a707655f0bbe4d4a66920e9e","impliedFormat":1},{"version":"a37c2194c586faa8979f50a5c5ca165b0903d31ee62a9fe65e4494aa099712c0","impliedFormat":1},{"version":"6602339ddc9cd7e54261bda0e70fb356d9cdc10e3ec7feb5fa28982f8a4d9e34","impliedFormat":1},{"version":"7ffaa736b8a04b0b8af66092da536f71ef13a5ef0428c7711f32b94b68f7c8c8","impliedFormat":1},{"version":"7b4930d666bbe5d10a19fcc8f60cfa392d3ad3383b7f61e979881d2c251bc895","impliedFormat":1},{"version":"46342f04405a2be3fbfb5e38fe3411325769f14482b8cd48077f2d14b64abcfb","impliedFormat":1},{"version":"8fa675c4f44e6020328cf85fdf25419300f35d591b4f56f56e00f9d52b6fbb3b","impliedFormat":1},{"version":"ba98f23160cfa6b47ee8072b8f54201f21a1ee9addc2ef461ebadf559fe5c43a","impliedFormat":1},{"version":"45a4591b53459e21217dc9803367a651e5a1c30358a015f27de0b3e719db816b","impliedFormat":1},{"version":"9ef22bee37885193b9fae7f4cad9502542c12c7fe16afe61e826cdd822643d84","impliedFormat":1},{"version":"b0451895b894c102eed19d50bd5fcb3afd116097f77a7d83625624fafcca8939","impliedFormat":1},{"version":"bce17120b679ff4f1be70f5fe5c56044e07ed45f1e555db6486c6ded8e1da1c8","impliedFormat":1},{"version":"7590477bfa2e309e677ff7f31cb466f377fcd0e10a72950439c3203175309958","impliedFormat":1},{"version":"3f9ebd554335d2c4c4e7dc67af342d37dc8f2938afa64605d8a93236022cc8a5","impliedFormat":1},{"version":"1c077c9f6c0bc02a36207994a6e92a8fbf72d017c4567f640b52bf32984d2392","impliedFormat":1},{"version":"600b42323925b32902b17563654405968aa12ee39e665f83987b7759224cc317","impliedFormat":1},{"version":"32c8f85f6b4e145537dfe61b94ddd98b47dbdd1d37dc4b7042a8d969cd63a1aa","impliedFormat":1},{"version":"2426ed0e9982c3d734a6896b697adf5ae93d634b73eb15b48da8106634f6d911","impliedFormat":1},{"version":"057431f69d565fb44c246f9f64eac09cf309a9af7afb97e588ebef19cc33c779","impliedFormat":1},{"version":"960d026ca8bf27a8f7a3920ee50438b50ec913d635aa92542ca07558f9c59eca","impliedFormat":1},{"version":"71f5d895cc1a8a935c40c070d3d0fade53ae7e303fd76f443b8b541dee19a90c","impliedFormat":1},{"version":"252eb4750d0439d1674ad0dc30d2a2a3e4655e08ad9e58a7e236b21e78d1d540","impliedFormat":1},{"version":"e344b4a389bb2dfa98f144f3f195387a02b6bdb69deed4a96d16cc283c567778","impliedFormat":1},{"version":"c6cdcd12d577032b84eed1de4d2de2ae343463701a25961b202cff93989439fb","impliedFormat":1},{"version":"3dc633586d48fcd04a4f8acdbf7631b8e4a334632f252d5707e04b299069721e","impliedFormat":1},{"version":"3322858f01c0349ee7968a5ce93a1ca0c154c4692aa8f1721dc5192a9191a168","impliedFormat":1},{"version":"6dde0a77adad4173a49e6de4edd6ef70f5598cbebb5c80d76c111943854636ca","impliedFormat":1},{"version":"09acacae732e3cc67a6415026cfae979ebe900905500147a629837b790a366b3","impliedFormat":1},{"version":"f7b622759e094a3c2e19640e0cb233b21810d2762b3e894ef7f415334125eb22","impliedFormat":1},{"version":"99236ea5c4c583082975823fd19bcce6a44963c5c894e20384bc72e7eccf9b03","impliedFormat":1},{"version":"f6688a02946a3f7490aa9e26d76d1c97a388e42e77388cbab010b69982c86e9e","impliedFormat":1},{"version":"9f642953aba68babd23de41de85d4e97f0c39ef074cb8ab8aa7d55237f62aff6","impliedFormat":1},{"version":"159d95163a0ed369175ae7838fa21a9e9e703de5fdb0f978721293dd403d9f4a","impliedFormat":1},{"version":"2d2ec3235e01474f45a68f28cf826c2f5228b79f7d474d12ca3604cdcfdac80c","impliedFormat":1},{"version":"6dd249868034c0434e170ba6e0451d67a0c98e5a74fd57a7999174ee22a0fa7b","impliedFormat":1},{"version":"9716553c72caf4ff992be810e650707924ec6962f6812bd3fbdb9ac3544fd38f","impliedFormat":1},{"version":"506bc8f4d2d639bebb120e18d3752ddeee11321fd1070ad2ce05612753c628d6","impliedFormat":1},{"version":"053c51bbc32db54be396654ab5ecd03a66118d64102ac9e22e950059bc862a5e","impliedFormat":1},{"version":"1977f62a560f3b0fc824281fd027a97ce06c4b2d47b408f3a439c29f1e9f7e10","impliedFormat":1},{"version":"627570f2487bd8d899dd4f36ecb20fe0eb2f8c379eff297e24caba0c985a6c43","impliedFormat":1},{"version":"0f6e0b1a1deb1ab297103955c8cd3797d18f0f7f7d30048ae73ba7c9fb5a1d89","impliedFormat":1},{"version":"0a051f254f9a16cdde942571baab358018386830fed9bdfff42478e38ba641ce","impliedFormat":1},{"version":"17269f8dfc30c4846ab7d8b5d3c97ac76f50f33de96f996b9bf974d817ed025b","impliedFormat":1},{"version":"9e82194af3a7d314ccbc64bb94bfb62f4bfea047db3422a7f6c5caf2d06540a9","impliedFormat":1},{"version":"083d6f3547ccbf25dfa37b950c50bee6691ed5c42107f038cc324dbca1e173ae","impliedFormat":1},{"version":"952a9eab21103b79b7a6cca8ad970c3872883aa71273f540285cad360c35da40","impliedFormat":1},{"version":"8ba48776335db39e0329018c04486907069f3d7ee06ce8b1a6134b7d745271cc","impliedFormat":1},{"version":"e6d5809e52ed7ef1860d1c483e005d1f71bab36772ef0fd80d5df6db1da0e815","impliedFormat":1},{"version":"893e5cfbae9ed690b75b8b2118b140665e08d182ed8531e1363ec050905e6cb2","impliedFormat":1},{"version":"6ae7c7ada66314a0c3acfbf6f6edf379a12106d8d6a1a15bd35bd803908f2c31","impliedFormat":1},{"version":"e4b1e912737472765e6d2264b8721995f86a463a1225f5e2a27f783ecc013a7b","impliedFormat":1},{"version":"97146bbe9e6b1aab070510a45976faaf37724c747a42d08563aeae7ba0334b4f","impliedFormat":1},{"version":"c40d552bd2a4644b0617ec2f0f1c58618a25d098d2d4aa7c65fb446f3c305b54","impliedFormat":1},{"version":"09e64dea2925f3a0ef972d7c11e7fa75fec4c0824e9383db23eacf17b368532f","impliedFormat":1},{"version":"424ddba00938bb9ae68138f1d03c669f43556fc3e9448ed676866c864ca3f1d6","impliedFormat":1},{"version":"a0fe12181346c8404aab9d9a938360133b770a0c08b75a2fce967d77ca4b543f","impliedFormat":1},{"version":"3cc6eb7935ff45d7628b93bb6aaf1a32e8cb3b24287f9e75694b607484b377b3","impliedFormat":1},{"version":"ced02e78a2e10f89f4d70440d0a8de952a5946623519c54747bc84214d644bac","impliedFormat":1},{"version":"efd463021ccc91579ed8ae62584176baab2cd407c555c69214152480531a2072","impliedFormat":1},{"version":"29647c3b79320cfeecb5862e1f79220e059b26db2be52ea256df9cf9203fb401","impliedFormat":1},{"version":"c87c29ad2df837f7bc528688d96793758e0c4b1965f5ca520a8c287999565f66","impliedFormat":1},{"version":"e8cdefd2dc293cb4866ee8f04368e7001884650bb0f43357c4fe044cc2e1674f","impliedFormat":1},{"version":"582a3578ebba9238eb0c5d30b4d231356d3e8116fea497119920208fb48ccf85","impliedFormat":1},{"version":"185eae4a1e8a54e38f36cd6681cfa54c975a2fc3bc2ba6a39bf8163fac85188d","impliedFormat":1},{"version":"0c0a02625cf59a0c7be595ccc270904042bea523518299b754c705f76d2a6919","impliedFormat":1},{"version":"f2c999522aa544d93920205e05e11a5d43332f95ec35bd8a17025a823035bc56","impliedFormat":1},{"version":"cee72255e129896f0240ceb58c22e207b83d2cc81d8446190d1b4ef9b507ccd6","impliedFormat":1},{"version":"3b54670e11a8d3512f87e46645aa9c83ae93afead4a302299a192ac5458aa586","impliedFormat":1},{"version":"c2fc4d3a130e9dc0e40f7e7d192ef2494a39c37da88b5454c8adf143623e5979","impliedFormat":1},{"version":"2e693158fc1eedba3a5766e032d3620c0e9c8ad0418e4769be8a0f103fdb52cd","impliedFormat":1},{"version":"516275ccf3e66dc391533afd4d326c44dd750345b68bb573fc592e4e4b74545f","impliedFormat":1},{"version":"07c342622568693847f6cb898679402dd19740f815fd43bec996daf24a1e2b85","impliedFormat":1},{"version":"fa40d705f9813843d47f19321591499f14d1a18fa5e8ca9beaee5aac633c3d0d","impliedFormat":1},{"version":"a7a6330fb015f72d821e23004e63a3827e0c632b614ef3a310b3c81b66de61fd","impliedFormat":1},{"version":"89968316b7069339433bd42d53fe56df98b6990783dfe00c9513fb4bd01c2a1c","impliedFormat":1},{"version":"a4096686f982f6977433ee9759ecbef49da29d7e6a5d8278f0fbc7b9f70fce12","impliedFormat":1},{"version":"62e62a477c56cda719013606616dd856cfdc37c60448d0feb53654860d3113bb","impliedFormat":1},{"version":"207c107dd2bd23fa9febac2fe05c7c72cdac02c3f57003ab2e1c6794a6db0c05","impliedFormat":1},{"version":"55133e906c4ddabecdfcbc6a2efd4536a3ac47a8fa0a3fe6d0b918cac882e0d4","impliedFormat":1},{"version":"2147f8d114cf58c05106c3dccea9924d069c69508b5980ed4011d2b648af2ffe","impliedFormat":1},{"version":"2eb4012a758b9a7ba9121951d7c4b9f103fe2fc626f13bec3e29037bb9420dc6","impliedFormat":1},{"version":"fe61f001bd4bd0a374daa75a2ba6d1bb12c849060a607593a3d9a44e6b1df590","impliedFormat":1},{"version":"cfe8221c909ad721b3da6080570553dea2f0e729afbdbcf2c141252cf22f39b5","impliedFormat":1},{"version":"34e89249b6d840032b9acdec61d136877f84f2cd3e3980355b8a18f119809956","impliedFormat":1},{"version":"6f36ff8f8a898184277e7c6e3bf6126f91c7a8b6a841f5b5e6cb415cfc34820e","impliedFormat":1},{"version":"4b6378c9b1b3a2521316c96f5c777e32a1b14d05b034ccd223499e26de8a379c","impliedFormat":1},{"version":"07be5ae9bf5a51f3d98ffcfacf7de2fe4842a7e5016f741e9fad165bb929be93","impliedFormat":1},{"version":"cb1b37eda1afc730d2909a0f62cac4a256276d5e62fea36db1473981a5a65ab1","impliedFormat":1},{"version":"c873a6d6019f0b93889e54bf3dfb133120d5adba79c0a31d2400d1b5d0f2a2a2","impliedFormat":1},{"version":"471386a0a7e4eb88c260bdde4c627e634a772bf22f830c4ec1dad823154fd6f5","impliedFormat":1},{"version":"108314a60f3cb2454f2d889c1fb8b3826795399e5d92e87b2918f14d70c01e69","impliedFormat":1},{"version":"d75cc838286d6b1260f0968557cd5f28495d7341c02ac93989fb5096deddfb47","impliedFormat":1},{"version":"d531dc11bb3a8a577bd9ff83e12638098bfc9e0856b25852b91aac70b0887f2a","impliedFormat":1},{"version":"19968b998a2ab7dfd39de0c942fc738b2b610895843fec25477bc393687babd8","impliedFormat":1},{"version":"c0e6319f0839d76beed6e37b45ec4bb80b394d836db308ae9db4dea0fe8a9297","impliedFormat":1},{"version":"1a7b11be5c442dab3f4af9faf20402798fddf1d3c904f7b310f05d91423ba870","impliedFormat":1},{"version":"079d3f1ddcaf6c0ff28cfc7851b0ce79fcd694b3590afa6b8efa6d1656216924","impliedFormat":1},{"version":"2c817fa37b3d2aa72f01ce4d3f93413a7fbdecafe1b9fb7bd7baaa1bbd46eb08","impliedFormat":1},{"version":"682203aed293a0986cc2fccc6321d862742b48d7359118ac8f36b290d28920d2","impliedFormat":1},{"version":"7406d75a4761b34ce126f099eafe6643b929522e9696e5db5043f4e5c74a9e40","impliedFormat":1},{"version":"7e9c4e62351e3af1e5e49e88ebb1384467c9cd7a03c132a3b96842ccdc8045c4","impliedFormat":1},{"version":"ea1f9c60a912065c08e0876bd9500e8fa194738855effb4c7962f1bfb9b1da86","impliedFormat":1},{"version":"903f34c920e699dacbc483780b45d1f1edcb1ebf4b585a999ece78e403bb2db3","impliedFormat":1},{"version":"100ebfd0470433805c43be5ae377b7a15f56b5d7181c314c21789c4fe9789595","impliedFormat":1},{"version":"12533f60d36d03d3cf48d91dc0b1d585f530e4c9818a4d695f672f2901a74a86","impliedFormat":1},{"version":"57b555a83466fb289968a1713f965f1a2bb3a91cc34d1fa21af8ebdef7fc872a","impliedFormat":1},{"version":"21d9968dad7a7f021080167d874b718197a60535418e240389d0b651dd8110e7","impliedFormat":1},{"version":"2ef7349b243bce723d67901991d5ad0dfc534da994af61c7c172a99ff599e135","impliedFormat":1},{"version":"fa103f65225a4b42576ae02d17604b02330aea35b8aaf889a8423d38c18fa253","impliedFormat":1},{"version":"1b9173f64a1eaee88fa0c66ab4af8474e3c9741e0b0bd1d83bfca6f0574b6025","impliedFormat":1},{"version":"1b212f0159d984162b3e567678e377f522d7bee4d02ada1cc770549c51087170","impliedFormat":1},{"version":"46bd71615bdf9bfa8499b9cfce52da03507f7140c93866805d04155fa19caa1b","impliedFormat":1},{"version":"86cb49eb242fe19c5572f58624354ffb8743ff0f4522428ebcabc9d54a837c73","impliedFormat":1},{"version":"fc2fb9f11e930479d03430ee5b6588c3788695372b0ab42599f3ec7e78c0f6d5","impliedFormat":1},{"version":"bb1e5cf70d99c277c9f1fe7a216b527dd6bd2f26b307a8ab65d24248fb3319f5","impliedFormat":1},{"version":"817547eacf93922e22570ba411f23e9164544dead83e379c7ae9c1cfc700c2cf","impliedFormat":1},{"version":"a728478cb11ab09a46e664c0782610d7dd5c9db3f9a249f002c92918ca0308f7","impliedFormat":1},{"version":"9e91ef9c3e057d6d9df8bcbfbba0207e83ef9ab98aa302cf9223e81e32fdfe8d","impliedFormat":1},{"version":"66d30ef7f307f95b3f9c4f97e6c1a5e4c462703de03f2f81aca8a1a2f8739dbd","impliedFormat":1},{"version":"293ca178fd6c23ed33050052c6544c9d630f9d3b11d42c36aa86218472129243","impliedFormat":1},{"version":"90a4be0e17ba5824558c38c93894e7f480b3adf5edd1fe04877ab56c56111595","impliedFormat":1},{"version":"fadd55cddab059940934df39ce2689d37110cfe37cc6775f06b0e8decf3092d7","impliedFormat":1},{"version":"adb906b7794a71185220332532dcbdd09527e0dd3ce9f0b9be0a88c56bbb7e9e","impliedFormat":1},{"version":"b4f3b4e20e2193179481ab325b8bd0871b986e1e8a8ed2961ce020c2dba7c02d","impliedFormat":1},{"version":"41744c67366a0482db029a21f0df4b52cd6f1c85cbc426b981b83b378ccb6e65","impliedFormat":1},{"version":"c3f3cf7561dd31867635c22f3c47c8491af4cfa3758c53e822a136828fc24e5d","impliedFormat":1},{"version":"a88ddea30fae38aa071a43b43205312dc5ff86f9e21d85ba26b14690dc19d95e","impliedFormat":1},{"version":"34fc71db924616ba097a0cb6cddc2ece273a27673dd3b206abf3f3b5d63bcace","impliedFormat":1},{"version":"5515f17f45c6aafe6459afa3318bba040cb466a8d91617041566808a5fd77a44","impliedFormat":1},{"version":"4df1f0c17953b0450aa988c9930061f8861b114e1649e1a16cfd70c5cbdf8d83","impliedFormat":1},{"version":"441104b363d80fe57eb79a50d495e0b7e3ebeb45a5f0d1a4067d71ef75e8fbfa","impliedFormat":1},"1769b9159823841131592b6e60d37e5ed8cef672c40720f430631ff189a6bb6c","693e76ba4d8c34330f2716083957d579929fc60fdc73a1cc4cff0078cf0916e1","86056b1db212b27439ab4c5431dc1dd4d3260cd496ba01c1bff75417bae6da36","b65cdce4ccf02e38849bc93ece32d51d3b730088f4bc3bb95dc490d20f1f3c28","54ed9bbe6198cf626ca6ab7a219f03be5e806eca4452c9155d8f46364be36dd7","feff88a4bbcad546b528c0e1275abc8e84de93ec1fb973c0bfa92a77203ca17e","cb1c887f968716f0e089008b9513f625dd96d619f2ee6ec90f98aea6adcf5c7e","6f5394af1c9dcac871ceee990bfc4c408e7bdd3554a60865e0ea108503103bfc","511408c2accf44e1ef48d3b9b10ae78f29608075e18718ab2db186db20603f9b","fc591eb0952e7cc93f8586d6c3f2567f58236f2a596b8f2d38baff53f46d3496","7ad3c6b9cbd4b0acee84e2c141abb6da4453c7bd62f2ec57b39bb569f4e38923","d73d1ca11cf67236128355e04896419e30bc35dabc5acd5d322aa2639f89f55d","8f35609338f7f85a99f7a520dfb76ad7421fba5ca682be8d6888643c68c4192d","0f9916ae0a4f28ae1d0277ef11831404fabeb2f3915099ceb95fd83e2875a1f9","e1928376912fdd1660d401d8829f6386bba58e9df618d71769b67d6f4c2be8e3","dd07e9cb553bfa3d48b6b8b530f87c5ef2d8e7c815ddd32ad8cf8207ace80da1","0f5665121ee776a07e933461b96e0d3983b4562af205a7a814521fbaa95fd6a6","9ff92161b58e93b69139545546c898693cd92e7cd5a0cde8c5a6a4e3c404e16a","3390402faa8aa3ba480f2569eceeddc3b5f9a31f44fd5ba9ce14c033b5b8f1a0","d0dac9064cbcb6ce920b058171f44fb51f1624973dd4d2cc3b4c2ad665ace855","0e59f8ee593e95e0ab662d35f9862902b6f58ade7a72c1db84e61f42f1789efb","bb417828f06afe47ae3578393fc5ff7482d8b8d9662c3baed26c090462501a46","d15585274e68b3de1c8d72be5b46a63fe74d0c02f0d41ad5564f04d5956b33ae","01b024cee0314d2ef677f10887c567fa44f25d3cc30537db4534417cf1088c4f","bf64f97bc6142d006111e87b3b228dcf3a4818b7462ed9b7ab7bf1b9faa6e3de","9403b2d561ce4b98ca797d904595d5cd4ef28129be7f6bacff4bf27a93bc4ad0","531e9cb6122f363241e6e7b289abccb1720db00e010e355d2804e077e803a885",{"version":"b6e995b5ef6661f5636ff738e67e4ec90150768ef119ad74b473c404304408a1","impliedFormat":1},{"version":"5d470930bf6142d7cbda81c157869024527dc7911ba55d90b8387ef6e1585aa1","impliedFormat":1},{"version":"074483fdbf20b30bd450e54e6892e96ea093430c313e61be5fdfe51588baa2d6","impliedFormat":1},{"version":"b7e6a6a3495301360edb9e1474702db73d18be7803b3f5c6c05571212acccd16","impliedFormat":1},{"version":"aa7527285c94043f21baf6e337bc60a92c20b6efaa90859473f6476954ac5f79","impliedFormat":1},{"version":"dd3be6d9dcd79e46d192175a756546630f2dc89dab28073823c936557b977f26","impliedFormat":1},{"version":"8d0566152618a1da6536c75a5659c139522d67c63a9ae27e8228d76ab0420584","impliedFormat":1},{"version":"ba06bf784edafe0db0e2bd1f6ecf3465b81f6b1819871bf190a0e0137b5b7f18","impliedFormat":1},{"version":"a0500233cb989bcb78f5f1a81f51eabc06b5c39e3042c560a7489f022f1f55a3","impliedFormat":1},{"version":"220508b3fb6b773f49d8fb0765b04f90ef15caacf0f3d260e3412ed38f71ef09","impliedFormat":1},{"version":"1ad113089ad5c188fec4c9a339cb53d1bcbb65682407d6937557bb23a6e1d4e5","impliedFormat":1},{"version":"e56427c055602078cbf0e58e815960541136388f4fc62554813575508def98b6","impliedFormat":1},{"version":"1f58b0676a80db38df1ce19d15360c20ce9e983b35298a5d0b4aa4eb4fb67e0f","impliedFormat":1},{"version":"3d67e7eb73c6955ee27f1d845cae88923f75c8b0830d4b5440eea2339958e8ec","impliedFormat":1},{"version":"11fec302d58b56033ab07290a3abc29e9908e29d504db9468544b15c4cd7670d","impliedFormat":1},{"version":"c66d6817c931633650edf19a8644eea61aeeb84190c7219911cefa8ddea8bd9a","impliedFormat":1},{"version":"ab1359707e4fc610c5f37f1488063af65cda3badca6b692d44b95e8380e0f6c2","impliedFormat":1},{"version":"37deda160549729287645b3769cf126b0a17e7e2218737352676705a01d5957e","impliedFormat":1},{"version":"d80ffdd55e7f4bc69cde66933582b8592d3736d3b0d1d8cc63995a7b2bcca579","impliedFormat":1},{"version":"c9b71952b2178e8737b63079dba30e1b29872240b122905cbaba756cb60b32f5","impliedFormat":1},{"version":"b596585338b0d870f0e19e6b6bcbf024f76328f2c4f4e59745714e38ee9b0582","impliedFormat":1},{"version":"e6717fc103dfa1635947bf2b41161b5e4f2fabbcaf555754cc1b4340ec4ca587","impliedFormat":1},{"version":"c36186d7bdf1f525b7685ee5bf639e4b157b1e803a70c25f234d4762496f771f","impliedFormat":1},{"version":"026726932a4964341ab8544f12b912c8dfaa388d2936b71cc3eca0cffb49cc1d","impliedFormat":1},{"version":"83188d037c81bd27076218934ba9e1742ddb69cd8cc64cdb8a554078de38eb12","impliedFormat":1},{"version":"7d82f2d6a89f07c46c7e3e9071ab890124f95931d9c999ba8f865fa6ef6cbf72","impliedFormat":1},{"version":"4fc523037d14d9bb6ddb586621a93dd05b6c6d8d59919a40c436ca3ac29d9716","impliedFormat":1},"f3c2da52e12f979ff580962787ff1699c76d05d5f29c0db1cc0f16a7b29d2c46","e14d1f4e3fab05d27054d0e1eb53f9a58ede955c9631e5000fe895de6a0109fc","1a45555d2e9aa0d1ec19969f47c8f79606dd6fa30f234eb7c465e38a349a520c",{"version":"e749bbd37dadf82c9833278780527c717226e1e2c9bc7b2576c8ec1c40ec5647","impliedFormat":1},{"version":"cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133","impliedFormat":1},{"version":"6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf","impliedFormat":1},{"version":"e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520","impliedFormat":1},{"version":"73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334","impliedFormat":1},{"version":"fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee","impliedFormat":1},{"version":"4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d","impliedFormat":1},{"version":"31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714","impliedFormat":1},{"version":"7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b","impliedFormat":1},{"version":"ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae","impliedFormat":1},{"version":"f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9","impliedFormat":1},{"version":"d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7","impliedFormat":1},{"version":"7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a","impliedFormat":1},{"version":"d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496","impliedFormat":1},{"version":"b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9","impliedFormat":1},{"version":"04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977","impliedFormat":1},{"version":"57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8","impliedFormat":1},{"version":"badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38","impliedFormat":1},"f0ad8d7c3c37513bc44b861d4b5e1f581388b1d8e7ab1ce14409716fe7a3c426","85bc250ef09f771868f44ba20b23e255fc631d28c2674ed73e54cb01af5b8ca0","8300f685f43a72feead994aeeb4dd3bf8aa1c6a65871114911ae7da8e897e42f",{"version":"7b7a03a1eadb5d1ee250add7effa231948c210860d34be1987a137a39f19503c","signature":"11bc0695e0f7e70f2a06a1d5f11851f8cdb897f66f927f6954dcac017ce8e6c8"},"b506c6c097da610758984339cf46757e96ea0d1992472732ea3ec68ef2020645","725832c8a79465deaed5efd81e18bd786caa5559607c5474b49f5e6b2f0eb470","58d18a596748345246730e78e9f5f606f3c3ef2cb2e59af815f937efe4eb94a1",{"version":"a2455aab7a82ff00eafbc138398b44a1d10caa3975cf4247074f499b3ea899c2","signature":"51c5375681a951958ae6df1624763b555b57cbf2d9764b5d5f7e4c19962bfa0a"},{"version":"5d5c476fc8ed0c54ab7bc6eb65a2325ebfc81d123f17abd48be75606ecd21c48","signature":"57719d7e3834859a060733118d6b8383b68121eaabb91b29f7a17a5cfba3e695"},{"version":"25e5c8b73c6ad21f39e8e72f954090f30b431a993252bccea5bdad4a3d93c760","impliedFormat":1},{"version":"5bf595f68b7c1d46ae8385e3363c6e0d4695b6da58a84c6340489fc07ffc73f8","impliedFormat":1},{"version":"b87682ddc9e2c3714ca66991cdd86ff7e18cae6fd010742a93bd612a07d19697","impliedFormat":1},{"version":"87d3ab3f2edb68849714195c008bf9be6067b081ef5a199c9c32f743c6871522","impliedFormat":1},{"version":"86bf2bfe29d0bc3fbc68e64c25ea6eab9bcb3c518ae941012ed75b1e87d391ae","impliedFormat":1},{"version":"8d9c4957c4feed3de73c44eb472f5e44dfb0f0cb75db6ea00f38939bd77f6e84","impliedFormat":1},{"version":"00b4f8b82e78f658b7e269c95d07e55d391235ce34d432764687441177ae7f64","impliedFormat":1},{"version":"57880096566780d72e02a5b34d8577e78cdf072bfd624452a95d65bd8f07cbe0","impliedFormat":1},{"version":"10ac50eaf9eb62c048efe576592b14830a757f7ea7ed28ee8deafc19c9845297","impliedFormat":1},{"version":"e75af112e5487476f7c427945fbd76ca46b28285586ad349a25731d196222d56","impliedFormat":1},{"version":"e91adad3da69c366d57067fcf234030b8a05bcf98c25a759a7a5cd22398ac201","impliedFormat":1},{"version":"d7d6e1974124a2dad1a1b816ba2436a95f44feeda0573d6c9fb355f590cf9086","impliedFormat":1},{"version":"464413fcd7e7a3e1d3f2676dc5ef4ebe211c10e3107e126d4516d79439e4e808","impliedFormat":1},{"version":"18f912e4672327b3dd17d70e91da6fcd79d497ba01dde9053a23e7691f56908c","impliedFormat":1},{"version":"2974e2f06de97e1d6e61d1462b54d7da2c03b3e8458ee4b3dc36273bc6dda990","impliedFormat":1},{"version":"d8c1697db4bb3234ff3f8481545284992f1516bc712421b81ee3ef3f226ae112","impliedFormat":1},{"version":"59b6cce93747f7eb2c0405d9f32b77874e059d9881ec8f1b65ff6c068fcce6f2","impliedFormat":1},{"version":"e2c3c3ca3818d610599392a9431e60ec021c5d59262ecd616538484990f6e331","impliedFormat":1},{"version":"e3cd60be3c4f95c43420be67eaa21637585b7c1a8129f9b39983bbd294f9513c","impliedFormat":1},{"version":"d57be402cf1a3f1bd1852fc71b31ff54da497f64dcdcf8af9ad32435e3f32c1f","affectsGlobalScope":true,"impliedFormat":1},"64dbd15325fab4cc36a7f076c3d9576f99b3eb50aabd7aab480b4247a0c15efe","59bff517b44208505fb8e5eb85c5ff800d9d60f5fd5f8424b4afe3585c8f4470","98273e22f2353dfe543bce72921c2da7cf9ea1712e4e469dd1d211144d56f4d3","579dea90040a7a9430fea970cb9bee5c96a8feefa05524661d032bc446c56810","71ab86a742542f861d14e239026d5ee05c60723f9734ba83ca9369b6ea51250f","60c0babe5a1d30fe800b0a57c8cc50b987d2495c8374b16797146912a4e9639a","566d34f187a096af3ce589c442bd07a06bec38ddee81ec2b85da69f2f9314b15","f8081adb7e407e3e0cab1f7ab48ccaa1a869e5ec6666519d4aefd81474a06d8d","2821a97e50427e1a6c9ed4d572e91672a93c2ff621e34f6a698fe11ff0a8269a","0f54d3dd725bbe128eec929b20f10f15c6c9b6f31e25fc9b2f5d6991ee454033","3028702e1591a2c75ae5b98c046781a2358cd2f643681501f15ffa8527e3f7c9","a9a7f52349a991b14d1e6186b137799250320b75599970906d043a061d0dd9cd","5aeb1bbc365519735da0465db2e558732bc4c760cb55c7315c0f0127da84cb8d","736305ef9b9de16f85b4cfa05f13745c49fd6be7d93eddf509feb29bd7f761f1",{"version":"ba63131c5e91f797736444933af16ffa42f9f8c150d859ec65f568f037a416ea","impliedFormat":1},{"version":"aa99b580bd92dcb2802c9067534ebc32381f0e1f681a65366bcf3adae208a3a4","impliedFormat":1},{"version":"340a45cd77b41d8a6deda248167fa23d3dc67ec798d411bd282f7b3d555b1695","impliedFormat":1},{"version":"0e9aa853b5eb2ca09e0e3e3eb94cbd1d5fb3d682ab69817d4d11fe225953fc57","impliedFormat":1},{"version":"179683df1e78572988152d598f44297da79ac302545770710bba87563ce53e06","impliedFormat":1},{"version":"793c353144f16601da994fa4e62c09b7525836ce999c44f69c28929072ca206a","impliedFormat":1},{"version":"ff155930718467b27e379e4a195e4607ce277f805cad9d2fa5f4fd5dec224df6","affectsGlobalScope":true,"impliedFormat":1},{"version":"599ac4a84b7aa6a298731179ec1663a623ff8ac324cdc1dabb9c73c1259dc854","impliedFormat":1},{"version":"3d348edaf4ef0169b476e42e1489ddc800ae03bd5dd3acb12354225718170774","impliedFormat":1},{"version":"585bc61f439c027640754dd26e480afa202f33e51db41ee283311a59c12c62e7","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},"91db42b95ee64123b1efdfbdbdf40c553c6ce2b63213512a7d87aba4895b80b5","dad27252d6fd5261cc005614a057607d42b2291d19507b54c34ea45b376fb384","76ccf52686a426bfde74ca6ef217336c07e23347e65bd42ae8f54925999d7a18","69e25dd6fe9889d994fa80e803571011e63d4f4204a3077f5325b2e0741de0be","e136c0dfb49f599772c143d797c7b17cc37409d9f126181e00bbb99f657d93ce","f0c5eae0f58bdb032b93e0132d4cd5185c51e2c0530f3f81008a517377a60035",{"version":"0bf811dcbddc95e2551f704cfd2afc267bf619f8b8f2b7bdbb94df96ec3cbfe3","impliedFormat":1},{"version":"243e3c271aff347e8461255546750cf7d413585016c510e33907e42a754d6937","impliedFormat":1},{"version":"7c14e702387296711c1a829bc95052ff02f533d4aa27d53cc0186c795094a3a9","impliedFormat":1},{"version":"4c72d080623b3dcd8ebd41f38f7ac7804475510449d074ca9044a1cbe95517ae","impliedFormat":1},{"version":"579f8828da42ae02db6915a0223d23b0da07157ff484fecdbf8a96fffa0fa4df","impliedFormat":1},{"version":"3f17ea1a2d703cfe38e9fecf8d8606717128454d2889cef4458a175788ad1b60","impliedFormat":1},{"version":"3ae3b86c48ae3b092e5d5548acbf4416b427fed498730c227180b5b1a8aa86e3","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"a3d3f704c5339a36da3ca8c62b29072f87e86c783b8452d235992142ec71aa2d","impliedFormat":1},"fb615381faee80445f54fe65de6db16c436b955539725cc9af5ddea63288a5c5","df2c14fc48201875f6260814380e9d93758f710f3a73e191a4944ac0259d838a","d54f299ef37a523e6c54142ba5fd4950d84a29c83484e86abea9e35f67200302","9a1765ec90a87e4d8150b1d4fda03ac75b19e74e60681559232397a6324936a8","759bf5638061a21a79d6abcac2e7fb7340411487d88aa917b508e87f289140f6",{"version":"03c92769f389dbd9e45232f7eb01c3e0f482b62555aaf2029dcbf380d5cee9e4","impliedFormat":1},{"version":"32d7f70fd3498bc76a46dab8b03af4215f445f490f8e213c80cf06b636a4e413","impliedFormat":1},"24e25cab152fcd3d0bf37e2740dc6013f4aeafafdbfeb3fe938dc9b3a6ecf281","ca39338bf507361203571d135cde04ab2df3576c8ba3d504dd189ca638ca047a","ec37c25eac46dd4f50a2b2c190a6646faa16d8fe2319b9f82032ae87569d0863","2a22159aa7e91ab4e96c3aab3ab37e5fac1d40894233b9150c078a30996063ff","c7670f0ff87e5752464137522021996c50338b7fc60afdd7ff2f9975b528b3b1","41bcf964f8e19b08bd113f49dcedbc95fbdb4a2741356307e99956b60f613c85","048be396f12f2a9e76f0b323c5df53d063cc1e618c1453808f542b0933ab7302","c53661430c24eb5e56513e37ebf7ab5c4f88d1ab9b28d77b810eab4f8c8e6b19","364ca59447389bbb62bfc0cd675a04b4204a5f4152a411d1a573cf61dbe88143","299e0b64933fe4578dde0ee808e167cc797e1e8700a20d98cf511bac59eceab6","196812e1b7a944e7839789e03af00ebb883db4e59e48eb3ceb211a25e3be07a6",{"version":"86cf314f55b037f6bf983219546aa86c0f50445907697576c1fa2f115b95520d","signature":"b82491e2990291580288c5602d4c017238977749d52b17391f0e45d9a29be644"},"1b2b9c1c5cc031c441786edc1db0df15af8b08446f8c9a85c017c161d653d8ae",{"version":"53477a1815e915b8c20222a2ac8f9e3de880a1e8c8dbf9dae529b3d2e2b4a53b","impliedFormat":1},{"version":"8a0ca47b5bc6ecaaa9a4355dc0ee2c64e4996358a819241bd565d04e8abeb315","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},"f445afb39764b7bfc939c56fb47547c4a32f1a97565733302af020cc8e64f955","598fc3051a2b63251b2326d374df8ae83def86fc9448c10e111fa62b72de5194","04907fae70a69ba243e76a4761c605e48d8181f5b477e88a6147295c4bab336d","7692ea3cc6b910a7e058b8e0905f0ab5d6d1a47918a25b9a4bc42d02e299d8a7","8eaddf448ad6252dcada4466fbef5caeacf63f6eff79f10774b8ae6cd52e8470",{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"e1028394c1cf96d5d057ecc647e31e457b919092f882ed0c7092152b077fed9d","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1}],"root":[[630,635],[777,803],[831,833],[852,860],[881,894],[906,911],[921,925],[928,940],[942,947]],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"emitDecoratorMetadata":true,"experimentalDecorators":true,"module":1,"noFallthroughCasesInSwitch":false,"noImplicitAny":true,"outDir":"./dist","removeComments":true,"rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strictBindCallApply":false,"strictNullChecks":true,"target":8},"referencedMap":[[628,1],[627,2],[948,3],[951,4],[406,3],[320,3],[58,3],[309,5],[310,5],[311,3],[312,6],[322,7],[313,3],[314,8],[315,3],[316,3],[317,5],[318,5],[319,5],[321,9],[329,10],[331,3],[328,3],[334,11],[332,3],[330,3],[326,12],[327,13],[333,3],[335,14],[323,3],[325,15],[324,16],[264,3],[267,17],[263,3],[453,3],[265,3],[266,3],[352,18],[337,18],[344,18],[341,18],[354,18],[345,18],[351,18],[336,19],[355,18],[358,20],[349,18],[339,18],[357,18],[342,18],[340,18],[350,18],[346,18],[356,18],[343,18],[353,18],[338,18],[348,18],[347,18],[365,21],[361,22],[360,3],[359,3],[364,23],[403,24],[59,3],[60,3],[61,3],[435,25],[63,26],[441,27],[440,28],[253,29],[254,26],[374,3],[283,3],[284,3],[375,30],[255,3],[376,3],[377,31],[62,3],[257,32],[258,3],[256,33],[259,32],[260,3],[262,34],[274,35],[275,3],[280,36],[276,3],[277,3],[278,3],[279,3],[281,3],[282,37],[288,38],[291,39],[289,3],[290,3],[308,40],[292,3],[293,3],[484,41],[273,42],[271,43],[269,44],[270,45],[272,3],[300,46],[294,3],[303,47],[296,48],[301,49],[299,50],[302,51],[297,52],[298,53],[286,54],[304,55],[287,56],[306,57],[307,58],[295,3],[261,3],[268,59],[305,60],[371,61],[366,3],[372,62],[367,63],[368,64],[369,65],[370,66],[373,67],[389,68],[388,69],[394,70],[386,3],[387,71],[390,68],[391,72],[393,73],[392,74],[395,75],[380,76],[381,77],[384,78],[383,78],[382,77],[385,77],[379,79],[397,80],[396,81],[399,82],[398,83],[400,84],[362,54],[363,85],[285,3],[401,86],[378,87],[402,88],[404,89],[405,90],[426,91],[427,92],[428,3],[429,93],[430,94],[439,95],[432,96],[436,97],[444,98],[442,6],[443,99],[433,100],[445,3],[447,101],[448,102],[449,103],[438,104],[434,105],[458,106],[446,107],[473,108],[431,109],[474,110],[471,111],[472,6],[496,112],[421,113],[417,114],[419,115],[470,116],[412,117],[460,118],[459,3],[420,119],[467,120],[424,121],[468,3],[469,122],[422,123],[416,124],[423,125],[418,126],[411,3],[464,127],[477,128],[475,6],[407,6],[463,129],[408,13],[409,92],[410,130],[414,131],[413,132],[476,133],[415,134],[452,135],[450,101],[451,136],[461,13],[462,137],[465,138],[480,139],[481,140],[478,141],[479,142],[482,143],[483,144],[485,145],[457,146],[454,147],[455,5],[456,136],[487,148],[486,149],[493,150],[425,6],[489,151],[488,6],[491,152],[490,3],[492,153],[437,154],[466,155],[495,156],[494,6],[918,157],[914,158],[913,159],[915,3],[916,160],[917,161],[919,162],[895,3],[899,163],[904,164],[896,6],[898,165],[897,3],[900,166],[902,167],[903,168],[905,169],[864,170],[865,171],[879,172],[867,173],[866,174],[861,175],[862,3],[863,3],[878,176],[869,177],[870,177],[871,177],[872,177],[874,178],[873,177],[875,179],[876,180],[868,3],[877,181],[509,182],[500,183],[506,3],[497,3],[498,184],[501,185],[502,6],[503,186],[499,187],[504,188],[505,189],[507,190],[508,3],[629,191],[626,3],[950,3],[920,3],[624,192],[623,193],[618,194],[625,195],[619,3],[956,196],[912,197],[620,3],[880,198],[561,199],[562,199],[563,200],[515,201],[564,202],[565,203],[566,204],[510,3],[513,205],[511,3],[512,3],[567,206],[568,207],[569,208],[570,209],[571,210],[572,211],[573,211],[574,212],[575,213],[576,214],[577,215],[516,3],[514,3],[578,216],[579,217],[580,218],[614,219],[581,220],[582,3],[583,221],[584,222],[585,223],[586,224],[587,225],[588,226],[589,227],[590,228],[591,229],[592,229],[593,230],[594,3],[595,231],[596,232],[598,233],[597,234],[599,235],[600,236],[601,237],[602,238],[603,239],[604,240],[605,241],[606,242],[607,243],[608,244],[609,245],[610,246],[611,247],[517,3],[518,3],[519,3],[558,248],[559,3],[560,3],[612,249],[613,250],[927,251],[926,252],[901,253],[616,3],[617,3],[615,254],[622,255],[621,256],[678,257],[669,3],[670,3],[671,3],[672,3],[673,3],[674,3],[675,3],[676,3],[677,3],[520,3],[949,3],[821,258],[822,258],[823,258],[829,259],[824,258],[825,258],[826,258],[827,258],[828,258],[812,260],[811,3],[830,261],[818,3],[814,262],[805,3],[804,3],[806,3],[807,258],[808,263],[820,264],[809,258],[810,258],[815,265],[816,266],[817,258],[813,3],[819,3],[639,3],[760,267],[764,267],[763,267],[761,267],[762,267],[765,267],[642,267],[654,267],[643,267],[656,267],[658,267],[652,267],[651,267],[653,267],[657,267],[659,267],[644,267],[655,267],[645,267],[647,268],[648,267],[649,267],[650,267],[666,267],[665,267],[768,269],[660,267],[662,267],[661,267],[663,267],[664,267],[767,267],[766,267],[667,267],[750,267],[749,267],[679,270],[680,270],[682,267],[727,267],[748,267],[683,270],[728,267],[725,267],[729,267],[684,267],[685,267],[686,270],[730,267],[724,270],[681,270],[731,267],[687,270],[732,267],[712,267],[688,270],[689,267],[690,267],[722,270],[693,267],[692,267],[733,267],[734,271],[735,270],[695,267],[697,267],[698,267],[704,267],[705,267],[706,267],[751,267],[699,270],[736,267],[723,270],[700,267],[701,267],[737,267],[702,267],[694,270],[738,267],[721,267],[739,267],[703,270],[707,267],[708,267],[726,270],[740,267],[741,267],[720,272],[696,267],[742,270],[743,267],[744,267],[745,267],[746,270],[709,267],[747,267],[713,267],[710,270],[711,270],[691,267],[714,267],[717,267],[715,267],[716,267],[668,267],[758,267],[752,267],[753,267],[755,267],[756,267],[754,267],[759,267],[757,267],[641,273],[776,274],[774,275],[775,276],[773,277],[772,267],[771,278],[638,3],[640,3],[636,3],[769,3],[770,279],[646,273],[637,3],[955,280],[941,175],[953,281],[954,282],[719,283],[718,3],[952,284],[57,3],[252,285],[225,3],[203,286],[201,286],[251,287],[216,288],[215,288],[116,289],[67,290],[223,289],[224,289],[226,291],[227,289],[228,292],[127,293],[229,289],[200,289],[230,289],[231,294],[232,289],[233,288],[234,295],[235,289],[236,289],[237,289],[238,289],[239,288],[240,289],[241,289],[242,289],[243,289],[244,296],[245,289],[246,289],[247,289],[248,289],[249,289],[66,287],[69,292],[70,292],[71,292],[72,292],[73,292],[74,292],[75,292],[76,289],[78,297],[79,292],[77,292],[80,292],[81,292],[82,292],[83,292],[84,292],[85,292],[86,289],[87,292],[88,292],[89,292],[90,292],[91,292],[92,289],[93,292],[94,292],[95,292],[96,292],[97,292],[98,292],[99,289],[101,298],[100,292],[102,292],[103,292],[104,292],[105,292],[106,296],[107,289],[108,289],[122,299],[110,300],[111,292],[112,292],[113,289],[114,292],[115,292],[117,301],[118,292],[119,292],[120,292],[121,292],[123,292],[124,292],[125,292],[126,292],[128,302],[129,292],[130,292],[131,292],[132,289],[133,292],[134,303],[135,303],[136,303],[137,289],[138,292],[139,292],[140,292],[145,292],[141,292],[142,289],[143,292],[144,289],[146,292],[147,292],[148,292],[149,292],[150,292],[151,292],[152,289],[153,292],[154,292],[155,292],[156,292],[157,292],[158,292],[159,292],[160,292],[161,292],[162,292],[163,292],[164,292],[165,292],[166,292],[167,292],[168,292],[169,304],[170,292],[171,292],[172,292],[173,292],[174,292],[175,292],[176,289],[177,289],[178,289],[179,289],[180,289],[181,292],[182,292],[183,292],[184,292],[202,305],[250,289],[187,306],[186,307],[210,308],[209,309],[205,310],[204,309],[206,311],[195,312],[193,313],[208,314],[207,311],[194,3],[196,315],[109,316],[65,317],[64,292],[199,3],[191,318],[192,319],[189,3],[190,320],[188,292],[197,321],[68,322],[217,3],[218,3],[211,3],[214,288],[213,3],[219,3],[220,3],[212,323],[221,3],[222,3],[185,324],[198,325],[834,326],[54,3],[55,3],[11,3],[9,3],[10,3],[15,3],[14,3],[2,3],[16,3],[17,3],[18,3],[19,3],[20,3],[21,3],[22,3],[23,3],[3,3],[24,3],[25,3],[4,3],[26,3],[30,3],[27,3],[28,3],[29,3],[31,3],[32,3],[33,3],[5,3],[34,3],[35,3],[36,3],[37,3],[6,3],[41,3],[38,3],[39,3],[40,3],[42,3],[7,3],[43,3],[48,3],[49,3],[44,3],[45,3],[46,3],[47,3],[8,3],[56,3],[53,3],[50,3],[51,3],[52,3],[1,3],[13,3],[12,3],[536,327],[546,328],[535,327],[556,329],[527,330],[526,331],[555,332],[549,333],[554,334],[529,335],[543,336],[528,337],[552,338],[524,339],[523,332],[553,340],[525,341],[530,342],[531,3],[534,342],[521,3],[557,343],[547,344],[538,345],[539,346],[541,347],[537,348],[540,349],[550,332],[532,350],[533,351],[542,352],[522,353],[545,344],[544,342],[548,3],[551,354],[851,355],[836,3],[837,3],[838,3],[839,3],[835,3],[840,356],[841,3],[843,357],[842,356],[844,356],[845,357],[846,356],[847,3],[848,356],[849,3],[850,3],[793,358],[794,359],[782,360],[939,361],[925,362],[932,363],[924,364],[856,6],[632,6],[790,6],[923,365],[922,365],[937,366],[928,367],[791,6],[938,368],[795,369],[796,370],[781,371],[940,372],[852,373],[943,374],[779,3],[944,375],[853,3],[854,3],[633,376],[635,377],[631,371],[800,365],[798,365],[799,365],[802,378],[803,379],[801,380],[942,381],[889,365],[891,382],[892,383],[890,384],[885,365],[887,385],[888,386],[886,387],[634,371],[630,388],[788,365],[787,365],[777,365],[784,365],[789,365],[786,365],[785,365],[778,365],[780,365],[792,389],[797,390],[783,391],[881,365],[883,392],[884,393],[882,394],[908,365],[910,395],[911,396],[909,397],[893,398],[906,399],[907,400],[945,401],[894,402],[832,365],[947,365],[831,403],[833,365],[858,365],[857,365],[859,404],[860,405],[946,406],[855,407],[933,365],[935,408],[936,409],[934,410],[930,411],[929,412],[931,413],[921,414]],"semanticDiagnosticsPerFile":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956],"version":"5.9.3"} \ No newline at end of file diff --git a/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart b/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart index b0424d4c..79cdf93f 100644 --- a/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart +++ b/flutter/lib/features/recipes/presentation/recipe_detail_screen.dart @@ -646,6 +646,9 @@ class _IngredientPreviewRow extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final cs = theme.colorScheme; + final label = ingredient.productName.trim().isEmpty + ? 'Okänd ingrediens' + : ingredient.productName; final (icon, color) = ingredient.fromPantry ? (Icons.kitchen_outlined, cs.secondary) @@ -687,7 +690,7 @@ class _IngredientPreviewRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${ingredient.productName}' + '$label' '${ingredient.note != null ? ' (${ingredient.note})' : ''}' ' – $requiredStr', style: theme.textTheme.bodyMedium, diff --git a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart index a4da00fd..3aa5b2e2 100644 --- a/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart +++ b/flutter/lib/features/recipes/presentation/recipe_edit_screen.dart @@ -17,14 +17,18 @@ import '../domain/recipe_ingredient.dart'; class _EditableIngredient { int? productId; - String productName; + String? productName; + String rawName; + String? rawLine; final TextEditingController quantityCtrl; String unit; final TextEditingController noteCtrl; _EditableIngredient({ required this.productId, - required this.productName, + this.productName, + required this.rawName, + this.rawLine, required String quantity, required this.unit, String note = '', @@ -35,6 +39,10 @@ class _EditableIngredient { return _EditableIngredient( productId: ingredient.productId, productName: ingredient.productName, + rawName: ingredient.rawName.trim().isNotEmpty + ? ingredient.rawName + : (ingredient.productName ?? ''), + rawLine: ingredient.rawLine, quantity: formatQuantity(ingredient.quantity), unit: ingredient.unit, note: ingredient.note ?? '', @@ -135,6 +143,7 @@ class _RecipeEditScreenState extends ConsumerState { _EditableIngredient( productId: null, productName: '', + rawName: '', quantity: '', unit: 'st', ), @@ -154,16 +163,17 @@ class _RecipeEditScreenState extends ConsumerState { return context.l10n.recipeEditMinIngredients; } for (final ingredient in _ingredients) { - if (ingredient.productId == null) { - return context.l10n.recipeEditSelectProduct; + if (ingredient.productId == null && ingredient.rawName.trim().isEmpty) { + return 'Ange ingrediensnamn eller välj produkt'; } - final quantity = double.tryParse( - ingredient.quantityCtrl.text.trim().replaceAll(',', '.'), - ); - if (quantity == null || quantity < 0) { + final qtyText = ingredient.quantityCtrl.text.trim(); + final quantity = qtyText.isEmpty + ? null + : double.tryParse(qtyText.replaceAll(',', '.')); + if (qtyText.isNotEmpty && (quantity == null || quantity < 0)) { return context.l10n.recipeEditValidQuantity; } - if (ingredient.unit.trim().isEmpty) { + if (qtyText.isNotEmpty && ingredient.unit.trim().isEmpty) { return context.l10n.recipeEditSelectUnit; } } @@ -189,14 +199,20 @@ class _RecipeEditScreenState extends ConsumerState { final servings = int.tryParse(_servingsCtrl.text.trim()); final ingredients = _ingredients .map( - (ingredient) => { - 'productId': ingredient.productId, - 'quantity': double.parse( + (ingredient) { + final parsedQty = double.tryParse( ingredient.quantityCtrl.text.trim().replaceAll(',', '.'), - ), - 'unit': ingredient.unit, - if (ingredient.noteCtrl.text.trim().isNotEmpty) - 'note': ingredient.noteCtrl.text.trim(), + ); + return { + 'rawName': ingredient.rawName.trim(), + if ((ingredient.rawLine ?? '').trim().isNotEmpty) + 'rawLine': ingredient.rawLine, + if (ingredient.productId != null) 'productId': ingredient.productId, + if (parsedQty != null) 'quantity': parsedQty, + if (ingredient.unit.trim().isNotEmpty) 'unit': ingredient.unit, + if (ingredient.noteCtrl.text.trim().isNotEmpty) + 'note': ingredient.noteCtrl.text.trim(), + }; }, ) .toList(); @@ -401,7 +417,7 @@ class _RecipeEditScreenState extends ConsumerState { initialValue: ingredient.productId, isExpanded: true, decoration: const InputDecoration( - labelText: 'Produkt *', + labelText: 'Produkt (valfritt)', border: OutlineInputBorder(), ), items: _allProducts @@ -433,6 +449,21 @@ class _RecipeEditScreenState extends ConsumerState { }, ), const SizedBox(height: 12), + TextFormField( + initialValue: ingredient.rawName, + decoration: const InputDecoration( + labelText: 'Ingrediensnamn', + border: OutlineInputBorder(), + ), + onChanged: (value) => ingredient.rawName = value, + validator: (value) { + if (ingredient.productId == null && (value == null || value.trim().isEmpty)) { + return 'Ange namn eller välj produkt'; + } + return null; + }, + ), + const SizedBox(height: 12), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -446,11 +477,8 @@ class _RecipeEditScreenState extends ConsumerState { keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: (value) { - if (value == null || value.trim().isEmpty) { - return context.l10n.quantityHint; - } - if (double.tryParse(value.trim().replaceAll(',', '.')) == - null) { + if (value == null || value.trim().isEmpty) return null; + if (double.tryParse(value.trim().replaceAll(',', '.')) == null) { return context.l10n.invalidNumber; } return null;