diff --git a/package-lock.json b/package-lock.json index 0e90217..96aba50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3366,6 +3366,16 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.57.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", @@ -10885,6 +10895,7 @@ }, "devDependencies": { "@types/node": "^25.5.0", + "@types/ws": "^8.18.1", "@vitest/coverage-v8": "^4.1.1", "pino-pretty": "^13.1.3", "rimraf": "^6.1.3", diff --git a/packages/api/package.json b/packages/api/package.json index 610ffc5..c02ad9a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -41,6 +41,7 @@ }, "devDependencies": { "@types/node": "^25.5.0", + "@types/ws": "^8.18.1", "@vitest/coverage-v8": "^4.1.1", "pino-pretty": "^13.1.3", "rimraf": "^6.1.3", diff --git a/packages/api/src/modules/prices/prices.routes.ts b/packages/api/src/modules/prices/prices.routes.ts index cbf8257..84026bd 100644 --- a/packages/api/src/modules/prices/prices.routes.ts +++ b/packages/api/src/modules/prices/prices.routes.ts @@ -52,7 +52,7 @@ function toPriceRecordResponse(rawDoc: unknown) { price: doc.price, currency: doc.currency, quantity: doc.quantity, - unit: doc.unit, + unit: doc.unit as import('@meshitrack/shared').ServingUnit, pricePerUnit: doc.pricePerUnit, date: toIso(doc.date), ...(doc.receiptImageUrl ? { receiptImageUrl: doc.receiptImageUrl } : {}), diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts b/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts index b2abf7d..7fa5be0 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts @@ -6,7 +6,7 @@ import type { } from '@meshitrack/shared'; export class ShoppingListsRepository { - public async create(data: CreateShoppingListInput & { householdId: string; createdBy: string; status: string }) { + public async create(data: any) { const list = new ShoppingListModel(data); const saved = await list.save(); return saved.toObject(); diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts b/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts index 8117035..373759b 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts @@ -308,11 +308,11 @@ export default fp( // 4. Persist Collaborative WebSocket Handshakes - app.route({ - method: 'GET', - url: '/api/v1/households/:householdId/shopping-lists/:id/sync', - websocket: true, - handler: (socket, request) => { + app.get( + '/api/v1/households/:householdId/shopping-lists/:id/sync', + { websocket: true }, + (connection: any, request: any) => { + const socket = connection.socket; const listId = request.params.id; // Setup connection context @@ -323,7 +323,7 @@ export default fp( request.log.info({ listId }, 'Active client joined shopping list sync channel'); - socket.on('message', async (messageBuffer) => { + socket.on('message', async (messageBuffer: any) => { try { const payload = JSON.parse(messageBuffer.toString()); @@ -367,7 +367,7 @@ export default fp( request.log.info({ listId }, 'Client severed sync handshake connection'); }); } - }); + ); }, { name: 'shopping-lists-routes', diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.service.ts b/packages/api/src/modules/shopping-lists/shopping-lists.service.ts index a817813..c279eb5 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.service.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.service.ts @@ -255,8 +255,8 @@ export class ShoppingListsService { quantity: item.quantity, unit: item.unit as ServingUnit, purchasePrice: item.actualPrice || undefined, - storeId: item.storeId || list.preferredStoreId || undefined, - notes: item.notes, + storeId: (item.storeId || list.preferredStoreId || undefined) as string | undefined, + notes: item.notes || undefined, }, householdId, userId @@ -264,14 +264,14 @@ export class ShoppingListsService { addedCount++; // 2. Log final point-in-time pricing ledger entry if user input final price - if (item.actualPrice !== undefined && item.actualPrice > 0) { + if (item.actualPrice != null && item.actualPrice > 0) { const storeId = item.storeId || list.preferredStoreId; if (storeId) { await this.pricesService.recordPrice( { productId: item.productId, - storeId, - price: item.actualPrice, + storeId: storeId as string, + price: item.actualPrice as number, quantity: item.quantity, unit: item.unit as ServingUnit, currency: 'USD', diff --git a/packages/shared/src/validation/meal-plan.schemas.test.ts b/packages/shared/src/validation/meal-plan.schemas.test.ts index de38875..05bbb77 100644 --- a/packages/shared/src/validation/meal-plan.schemas.test.ts +++ b/packages/shared/src/validation/meal-plan.schemas.test.ts @@ -8,19 +8,19 @@ describe('MealPlan Schemas', () => { type: MealType.DINNER, recipeName: 'Chicken Salad', servings: 2, - perServingNutrition: { calories: 300, protein: 30, carbs: 10, fat: 15 } + perServingNutrition: { calories: 300, protein: 30, carbs: 10, fat: 15 }, }; const validDay = { date: '2026-05-18', meals: [validMeal], - dailyNutritionTotal: { calories: 600, protein: 60, carbs: 20, fat: 30 } + dailyNutritionTotal: { calories: 600, protein: 60, carbs: 20, fat: 30 }, }; const validPlan = { weekStartDate: '2026-05-18', days: Array(7).fill(validDay), - status: MealPlanStatus.DRAFT + status: MealPlanStatus.DRAFT, }; it('should validate a valid meal plan', () => { diff --git a/packages/shared/src/validation/nutrition-target.schemas.test.ts b/packages/shared/src/validation/nutrition-target.schemas.test.ts index fef5b53..8bb44bd 100644 --- a/packages/shared/src/validation/nutrition-target.schemas.test.ts +++ b/packages/shared/src/validation/nutrition-target.schemas.test.ts @@ -7,7 +7,7 @@ describe('NutritionTarget Schemas', () => { proteinG: 150, carbsG: 200, fatG: 70, - isActive: true + isActive: true, }; it('should validate a valid target', () => { diff --git a/packages/shared/src/validation/price-record.schemas.ts b/packages/shared/src/validation/price-record.schemas.ts index 24ae824..dfe1956 100644 --- a/packages/shared/src/validation/price-record.schemas.ts +++ b/packages/shared/src/validation/price-record.schemas.ts @@ -16,15 +16,17 @@ export const CreatePriceRecordSchema = z.object({ export const BulkPriceRecordInputSchema = z.object({ storeId: z.string().min(1), date: z.iso.datetime().optional(), - items: z.array( - z.object({ - productId: z.string().min(1), - price: z.number().positive(), - quantity: z.number().positive(), - unit: z.nativeEnum(ServingUnit), - notes: z.string().max(1000).trim().optional(), - }) - ).min(1), + items: z + .array( + z.object({ + productId: z.string().min(1), + price: z.number().positive(), + quantity: z.number().positive(), + unit: z.nativeEnum(ServingUnit), + notes: z.string().max(1000).trim().optional(), + }), + ) + .min(1), }); export const PriceHistoryQuerySchema = z.object({ @@ -70,7 +72,7 @@ export const PriceAnalyticsResponseSchema = z.object({ storeName: z.string(), avgTotal: z.number(), tripCount: z.number(), - }) + }), ), priceAlerts: z.array( z.object({ @@ -82,20 +84,20 @@ export const PriceAnalyticsResponseSchema = z.object({ currentPrice: z.number(), changePercent: z.number(), date: z.string(), - }) + }), ), spendingOverTime: z.array( z.object({ period: z.string(), total: z.number(), - }) + }), ), spendingByCategory: z.array( z.object({ category: z.string(), total: z.number(), avgPerItem: z.number(), - }) + }), ), }); diff --git a/packages/shared/src/validation/shopping-list.schemas.ts b/packages/shared/src/validation/shopping-list.schemas.ts index b26c00b..bdbe4df 100644 --- a/packages/shared/src/validation/shopping-list.schemas.ts +++ b/packages/shared/src/validation/shopping-list.schemas.ts @@ -22,16 +22,19 @@ export const ShoppingItemSchema = z.object({ export const CreateShoppingListSchema = z.object({ name: z.string().min(1).max(100).trim(), preferredStoreId: z.string().optional(), - items: z.array( - z.object({ - productId: z.string().optional(), - customName: z.string().optional(), - quantity: z.number().positive(), - unit: z.nativeEnum(ServingUnit), - notes: z.string().optional(), - category: z.nativeEnum(ProductCategory).optional(), - }) - ).optional().default([]), + items: z + .array( + z.object({ + productId: z.string().optional(), + customName: z.string().optional(), + quantity: z.number().positive(), + unit: z.nativeEnum(ServingUnit), + notes: z.string().optional(), + category: z.nativeEnum(ProductCategory).optional(), + }), + ) + .optional() + .default([]), }); export const UpdateShoppingListSchema = z.object({ @@ -41,17 +44,18 @@ export const UpdateShoppingListSchema = z.object({ items: z.array(ShoppingItemSchema).optional(), }); -export const AddShoppingItemSchema = z.object({ - productId: z.string().optional(), - customName: z.string().optional(), - quantity: z.number().positive(), - unit: z.nativeEnum(ServingUnit), - notes: z.string().max(500).trim().optional(), - category: z.nativeEnum(ProductCategory).optional(), -}).refine( - (data) => data.productId || data.customName, - { message: 'Must provide either a productId or customName' } -); +export const AddShoppingItemSchema = z + .object({ + productId: z.string().optional(), + customName: z.string().optional(), + quantity: z.number().positive(), + unit: z.nativeEnum(ServingUnit), + notes: z.string().max(500).trim().optional(), + category: z.nativeEnum(ProductCategory).optional(), + }) + .refine((data) => data.productId || data.customName, { + message: 'Must provide either a productId or customName', + }); export const UpdateShoppingItemSchema = z.object({ quantity: z.number().positive().optional(), @@ -68,10 +72,12 @@ export const ShoppingListResponseSchema = z.object({ name: z.string(), status: z.nativeEnum(ShoppingListStatus), items: z.array(ShoppingItemSchema), - createdFrom: z.object({ - type: z.nativeEnum(ShoppingListSourceType), - referenceId: z.string().optional(), - }).optional(), + createdFrom: z + .object({ + type: z.nativeEnum(ShoppingListSourceType), + referenceId: z.string().optional(), + }) + .optional(), mealPlanId: z.string().optional(), totalEstimatedCost: z.number().optional(), preferredStoreId: z.string().optional(), @@ -93,20 +99,22 @@ export const StoreComparisonResultSchema = z.object({ estimatedTotal: z.number(), itemsCovered: z.number(), itemsMissing: z.array(z.string()), - }) + }), ), - splitStoreOption: z.object({ - stores: z.array( - z.object({ - storeId: z.string(), - storeName: z.string(), - items: z.array(z.string()), - subtotal: z.number(), - }) - ), - estimatedTotal: z.number(), - savingsVsBestSingleStore: z.number(), - }).optional(), + splitStoreOption: z + .object({ + stores: z.array( + z.object({ + storeId: z.string(), + storeName: z.string(), + items: z.array(z.string()), + subtotal: z.number(), + }), + ), + estimatedTotal: z.number(), + savingsVsBestSingleStore: z.number(), + }) + .optional(), }); export const BasketStoreComparisonResponseSchema = StoreComparisonResultSchema; diff --git a/packages/web/src/app/(dashboard)/products/ProductModal.tsx b/packages/web/src/app/(dashboard)/products/ProductModal.tsx index 86dca00..56bf577 100644 --- a/packages/web/src/app/(dashboard)/products/ProductModal.tsx +++ b/packages/web/src/app/(dashboard)/products/ProductModal.tsx @@ -190,7 +190,7 @@ export function ProductModal({ {error &&
{error}
} -