import { PantryItemModel } from '../../schemas/pantry-item.schema.js'; import type { ItemStatus } from '@meshitrack/shared'; interface FindByHouseholdQuery { storageLocation?: string; status?: string; urgency?: string; productId?: string; cursor?: string; limit: number; } export class PantryRepository { public async findByHousehold(householdId: string, query: FindByHouseholdQuery) { const filter: Record = { householdId }; if (query.storageLocation) filter['storageLocation'] = query.storageLocation; if (query.status) { const statuses = query.status.split(',').filter(Boolean); filter['status'] = statuses.length === 1 ? statuses[0] : { $in: statuses }; } if (query.urgency) { const urgencies = query.urgency.split(',').filter(Boolean); filter['freshnessEstimate.urgency'] = urgencies.length === 1 ? urgencies[0] : { $in: urgencies }; } if (query.productId) filter['productId'] = query.productId; if (query.cursor) { const id = Buffer.from(query.cursor, 'base64').toString(); filter['_id'] = { $gt: id }; } const limit = query.limit; const items = await PantryItemModel.find(filter) .sort({ 'freshnessEstimate.daysRemaining': 1, _id: 1 }) .limit(limit + 1) .lean() .exec(); const hasMore = items.length > limit; const data = hasMore ? items.slice(0, limit) : items; const cursor = data.length > 0 ? Buffer.from(data[data.length - 1]!._id.toString()).toString('base64') : null; return { data, pagination: { cursor: hasMore ? cursor : null, hasMore } }; } public async findById(id: string, householdId: string) { return PantryItemModel.findOne({ _id: id, householdId }).lean().exec(); } public async findExpiringSoon(householdId: string, days: number, cursor?: string, limit = 20) { const cutoff = new Date(); cutoff.setDate(cutoff.getDate() + days); const filter: Record = { householdId, status: { $in: ['sealed', 'opened', 'prepared'] }, 'freshnessEstimate.estimatedExpiryDate': { $lte: cutoff }, 'freshnessEstimate.daysRemaining': { $gte: -3 }, }; if (cursor) { const id = Buffer.from(cursor, 'base64').toString(); filter['_id'] = { $gt: id }; } const items = await PantryItemModel.find(filter) .sort({ 'freshnessEstimate.daysRemaining': 1, _id: 1 }) .limit(limit + 1) .lean() .exec(); const hasMore = items.length > limit; const data = hasMore ? items.slice(0, limit) : items; const cursorVal = data.length > 0 ? Buffer.from(data[data.length - 1]!._id.toString()).toString('base64') : null; return { data, pagination: { cursor: hasMore ? cursorVal : null, hasMore } }; } public async findActiveByHousehold(householdId: string) { return PantryItemModel.find({ householdId, status: { $in: ['sealed', 'opened', 'prepared'] }, }) .lean() .exec(); } public async create(data: Record) { const doc = new PantryItemModel(data); const saved = await doc.save(); return saved.toObject(); } public async update(id: string, householdId: string, data: Record) { return PantryItemModel.findOneAndUpdate({ _id: id, householdId }, { $set: data }, { new: true }) .lean() .exec(); } public async updateFreshness( id: string, freshnessEstimate: Record, status?: string, ) { const update: Record = { freshnessEstimate }; if (status) update['status'] = status; return PantryItemModel.findByIdAndUpdate(id, { $set: update }).exec(); } public async delete(id: string, householdId: string) { return PantryItemModel.findOneAndDelete({ _id: id, householdId }).exec(); } public async getWasteStats(householdId: string, start: Date, end: Date) { return PantryItemModel.aggregate([ { $match: { householdId, status: { $in: ['consumed', 'discarded'] }, updatedAt: { $gte: start, $lte: end }, }, }, { $group: { _id: null, totalConsumed: { $sum: { $cond: [{ $eq: ['$status', 'consumed'] }, 1, 0] }, }, totalDiscarded: { $sum: { $cond: [{ $eq: ['$status', 'discarded'] }, 1, 0] }, }, }, }, ]).exec(); } public async getTopWastedProducts(householdId: string, start: Date, end: Date, limit = 5) { return PantryItemModel.aggregate([ { $match: { householdId, status: 'discarded', updatedAt: { $gte: start, $lte: end }, }, }, { $group: { _id: '$productId', productName: { $first: '$productName' }, count: { $sum: 1 }, }, }, { $sort: { count: -1 } }, { $limit: limit }, { $project: { productId: '$_id', productName: 1, count: 1, _id: 0, }, }, ]).exec(); } public async findByIds(ids: string[], householdId: string) { return PantryItemModel.find({ _id: { $in: ids }, householdId, }) .lean() .exec(); } public async bulkUpdateStatus( ids: string[], householdId: string, status: ItemStatus, extra: Record = {}, ) { const result = await PantryItemModel.updateMany( { _id: { $in: ids }, householdId }, { $set: { status, ...extra } }, ).exec(); return result.modifiedCount; } }