Phases 6-7
This commit is contained in:
parent
76a516a417
commit
029940b079
111 changed files with 17247 additions and 447 deletions
141
packages/api/src/modules/recipes/recipes.repository.ts
Normal file
141
packages/api/src/modules/recipes/recipes.repository.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import { RecipeModel } from '../../schemas/recipe.schema.js';
|
||||
import type { CreateRecipeInput, UpdateRecipeInput, RecipeQueryInput } from '@meshitrack/shared';
|
||||
import type { RecipeIngredient as StoredIngredient } from '@meshitrack/shared';
|
||||
|
||||
interface NutritionInfo {
|
||||
calories: number;
|
||||
protein: number;
|
||||
carbs: number;
|
||||
fat: number;
|
||||
fiber?: number;
|
||||
sugar?: number;
|
||||
sodium?: number;
|
||||
saturatedFat?: number;
|
||||
cholesterol?: number;
|
||||
}
|
||||
|
||||
interface ComputedFields {
|
||||
ingredients: StoredIngredient[];
|
||||
totalNutrition: NutritionInfo;
|
||||
perServingNutrition: NutritionInfo;
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export class RecipesRepository {
|
||||
public async findByHousehold(householdId: string, query: RecipeQueryInput) {
|
||||
const filter: Record<string, unknown> = { householdId, deletedAt: { $exists: false } };
|
||||
|
||||
if (query.q) filter['$text'] = { $search: query.q };
|
||||
if (query.cuisine) filter['cuisine'] = { $regex: query.cuisine, $options: 'i' };
|
||||
if (query.isFavorite !== undefined) filter['isFavorite'] = query.isFavorite;
|
||||
if (query.maxCalories !== undefined)
|
||||
filter['perServingNutrition.calories'] = { $lte: query.maxCalories };
|
||||
|
||||
if (query.tags) {
|
||||
const tagList = query.tags
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean);
|
||||
if (tagList.length > 0) filter['tags'] = { $all: tagList };
|
||||
}
|
||||
|
||||
if (query.cursor) {
|
||||
const id = Buffer.from(query.cursor, 'base64').toString();
|
||||
filter['_id'] = { $gt: id };
|
||||
}
|
||||
|
||||
const limit = query.limit;
|
||||
const items = await RecipeModel.find(filter)
|
||||
.sort({ _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 RecipeModel.findOne({ _id: id, householdId, deletedAt: { $exists: false } })
|
||||
.lean()
|
||||
.exec();
|
||||
}
|
||||
|
||||
public async findByProductId(
|
||||
householdId: string,
|
||||
productId: string,
|
||||
query: { cursor?: string; limit?: number },
|
||||
) {
|
||||
const filter: Record<string, unknown> = {
|
||||
householdId,
|
||||
'ingredients.productId': productId,
|
||||
deletedAt: { $exists: false },
|
||||
};
|
||||
|
||||
const limit = query.limit ?? 20;
|
||||
if (query.cursor) {
|
||||
const id = Buffer.from(query.cursor, 'base64').toString();
|
||||
filter['_id'] = { $gt: id };
|
||||
}
|
||||
|
||||
const items = await RecipeModel.find(filter)
|
||||
.sort({ _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 findAllByProductId(householdId: string, productId: string) {
|
||||
return RecipeModel.find({
|
||||
householdId,
|
||||
'ingredients.productId': productId,
|
||||
deletedAt: { $exists: false },
|
||||
})
|
||||
.lean()
|
||||
.exec();
|
||||
}
|
||||
|
||||
public async create(
|
||||
data: Omit<CreateRecipeInput, 'ingredients'>,
|
||||
computed: ComputedFields,
|
||||
householdId: string,
|
||||
createdBy: string,
|
||||
) {
|
||||
const recipe = new RecipeModel({ ...data, ...computed, householdId, createdBy });
|
||||
const saved = await recipe.save();
|
||||
return saved.toObject();
|
||||
}
|
||||
|
||||
public async update(
|
||||
id: string,
|
||||
householdId: string,
|
||||
data: Partial<UpdateRecipeInput>,
|
||||
computed?: Partial<ComputedFields>,
|
||||
) {
|
||||
const update: Record<string, unknown> = { ...data };
|
||||
if (computed) Object.assign(update, computed);
|
||||
return RecipeModel.findOneAndUpdate(
|
||||
{ _id: id, householdId, deletedAt: { $exists: false } },
|
||||
{ $set: update },
|
||||
{ new: true, lean: true },
|
||||
).exec();
|
||||
}
|
||||
|
||||
public async softDelete(id: string, householdId: string) {
|
||||
return RecipeModel.findOneAndUpdate(
|
||||
{ _id: id, householdId, deletedAt: { $exists: false } },
|
||||
{ $set: { deletedAt: new Date() } },
|
||||
{ new: true, lean: true },
|
||||
).exec();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue