- Project setup: package.json, tsconfig.json, jest.config.js, .env.example - Config: env.ts, constants.ts (ALLOWED_UNITS, bcrypt rounds, JWT, deletion windows) - DB: Knex connection, knexfile, migrations 001-006 (users, password_reset_tokens, pantry_items, recipes+recipe_ingredients, shopping_lists+items, deleted_records) - Seeds: 10 seeded recipes with full ingredient lists - Middleware: JWT authMiddleware (validates token + user existence), errorHandler - Utils: Pino logger, JWT sign helper, Zod validators for all request shapes - Services: authService (signup/signin/google/pwd-reset/soft-delete/restore), pantryService (CRUD + case-insensitive duplicate guard), recipeService (browse+filter+scale), shoppingListService (CRUD+merge logic), syncService (delta sync + tombstone cleanup), emailService (SendGrid) - Routes: /v1/auth, /v1/pantry, /v1/recipes, /v1/shopping-lists, /v1/sync - App: Express factory (createApp), server entry point - Jobs: node-cron daily hard-delete + tombstone cleanup - Tests: validators, utils, auth, pantry, recipes, shopping lists, sync
165 lines
7.0 KiB
TypeScript
165 lines
7.0 KiB
TypeScript
import type { Knex } from 'knex';
|
|
|
|
const recipes = [
|
|
{
|
|
name: 'Chocolate Chip Cookies',
|
|
servings: 24,
|
|
instructions: '1. Preheat oven to 375°F.\n2. Cream butter and sugars.\n3. Beat in eggs and vanilla.\n4. Mix in flour, baking soda, and salt.\n5. Stir in chocolate chips.\n6. Drop by spoonfuls onto baking sheet.\n7. Bake 9-11 minutes until golden.',
|
|
ingredients: [
|
|
{ item_name: 'All-Purpose Flour', quantity: 2.25, unit: 'cups' },
|
|
{ item_name: 'Butter', quantity: 1, unit: 'cups' },
|
|
{ item_name: 'Granulated Sugar', quantity: 0.75, unit: 'cups' },
|
|
{ item_name: 'Brown Sugar', quantity: 0.75, unit: 'cups' },
|
|
{ item_name: 'Eggs', quantity: 2, unit: 'pieces' },
|
|
{ item_name: 'Vanilla Extract', quantity: 1, unit: 'tsp' },
|
|
{ item_name: 'Baking Soda', quantity: 1, unit: 'tsp' },
|
|
{ item_name: 'Chocolate Chips', quantity: 2, unit: 'cups' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Classic Pancakes',
|
|
servings: 4,
|
|
instructions: '1. Mix dry ingredients.\n2. Whisk wet ingredients separately.\n3. Combine wet and dry.\n4. Cook on greased griddle over medium heat.\n5. Flip when bubbles form.',
|
|
ingredients: [
|
|
{ item_name: 'All-Purpose Flour', quantity: 1.5, unit: 'cups' },
|
|
{ item_name: 'Milk', quantity: 1.25, unit: 'cups' },
|
|
{ item_name: 'Eggs', quantity: 1, unit: 'pieces' },
|
|
{ item_name: 'Butter', quantity: 3, unit: 'tbsp' },
|
|
{ item_name: 'Baking Powder', quantity: 2, unit: 'tsp' },
|
|
{ item_name: 'Granulated Sugar', quantity: 1, unit: 'tbsp' },
|
|
{ item_name: 'Salt', quantity: 1, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Spaghetti Bolognese',
|
|
servings: 4,
|
|
instructions: '1. Brown ground beef.\n2. Add onion and garlic, cook until soft.\n3. Add tomatoes and simmer 30 minutes.\n4. Cook pasta.\n5. Serve sauce over pasta.',
|
|
ingredients: [
|
|
{ item_name: 'Spaghetti', quantity: 400, unit: 'g' },
|
|
{ item_name: 'Ground Beef', quantity: 500, unit: 'g' },
|
|
{ item_name: 'Onion', quantity: 1, unit: 'whole' },
|
|
{ item_name: 'Garlic', quantity: 3, unit: 'cloves' },
|
|
{ item_name: 'Canned Tomatoes', quantity: 2, unit: 'can' },
|
|
{ item_name: 'Olive Oil', quantity: 2, unit: 'tbsp' },
|
|
{ item_name: 'Salt', quantity: 1, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Caesar Salad',
|
|
servings: 2,
|
|
instructions: '1. Wash and chop romaine.\n2. Make dressing with garlic, lemon, and parmesan.\n3. Toss lettuce with dressing.\n4. Top with croutons and extra parmesan.',
|
|
ingredients: [
|
|
{ item_name: 'Romaine Lettuce', quantity: 1, unit: 'whole' },
|
|
{ item_name: 'Parmesan Cheese', quantity: 0.5, unit: 'cups' },
|
|
{ item_name: 'Garlic', quantity: 2, unit: 'cloves' },
|
|
{ item_name: 'Lemon', quantity: 1, unit: 'whole' },
|
|
{ item_name: 'Olive Oil', quantity: 3, unit: 'tbsp' },
|
|
{ item_name: 'Croutons', quantity: 1, unit: 'cups' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Banana Bread',
|
|
servings: 8,
|
|
instructions: '1. Preheat oven to 350°F.\n2. Mash bananas.\n3. Mix wet ingredients.\n4. Fold in dry ingredients.\n5. Pour into loaf pan.\n6. Bake 60-65 minutes.',
|
|
ingredients: [
|
|
{ item_name: 'Ripe Bananas', quantity: 3, unit: 'whole' },
|
|
{ item_name: 'All-Purpose Flour', quantity: 1.5, unit: 'cups' },
|
|
{ item_name: 'Butter', quantity: 0.33, unit: 'cups' },
|
|
{ item_name: 'Granulated Sugar', quantity: 0.75, unit: 'cups' },
|
|
{ item_name: 'Eggs', quantity: 1, unit: 'pieces' },
|
|
{ item_name: 'Baking Soda', quantity: 1, unit: 'tsp' },
|
|
{ item_name: 'Salt', quantity: 0.25, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Chicken Stir Fry',
|
|
servings: 4,
|
|
instructions: '1. Slice chicken and vegetables.\n2. Heat oil in wok.\n3. Cook chicken until done.\n4. Add vegetables and stir fry.\n5. Add sauce and toss.\n6. Serve over rice.',
|
|
ingredients: [
|
|
{ item_name: 'Chicken Breast', quantity: 500, unit: 'g' },
|
|
{ item_name: 'Bell Pepper', quantity: 2, unit: 'whole' },
|
|
{ item_name: 'Broccoli', quantity: 2, unit: 'cups' },
|
|
{ item_name: 'Soy Sauce', quantity: 3, unit: 'tbsp' },
|
|
{ item_name: 'Garlic', quantity: 3, unit: 'cloves' },
|
|
{ item_name: 'Vegetable Oil', quantity: 2, unit: 'tbsp' },
|
|
{ item_name: 'Rice', quantity: 2, unit: 'cups' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Guacamole',
|
|
servings: 4,
|
|
instructions: '1. Halve and pit avocados.\n2. Scoop flesh into bowl.\n3. Mash with fork.\n4. Add lime juice, salt, onion, cilantro.\n5. Mix and adjust seasoning.',
|
|
ingredients: [
|
|
{ item_name: 'Avocado', quantity: 3, unit: 'whole' },
|
|
{ item_name: 'Lime', quantity: 1, unit: 'whole' },
|
|
{ item_name: 'Red Onion', quantity: 0.25, unit: 'whole' },
|
|
{ item_name: 'Cilantro', quantity: 2, unit: 'tbsp' },
|
|
{ item_name: 'Salt', quantity: 0.5, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Tomato Soup',
|
|
servings: 4,
|
|
instructions: '1. Sauté onion and garlic.\n2. Add tomatoes and broth.\n3. Simmer 20 minutes.\n4. Blend until smooth.\n5. Season with salt and pepper.',
|
|
ingredients: [
|
|
{ item_name: 'Canned Tomatoes', quantity: 2, unit: 'can' },
|
|
{ item_name: 'Onion', quantity: 1, unit: 'whole' },
|
|
{ item_name: 'Garlic', quantity: 2, unit: 'cloves' },
|
|
{ item_name: 'Vegetable Broth', quantity: 2, unit: 'cups' },
|
|
{ item_name: 'Olive Oil', quantity: 2, unit: 'tbsp' },
|
|
{ item_name: 'Salt', quantity: 1, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'French Toast',
|
|
servings: 2,
|
|
instructions: '1. Whisk eggs, milk, and cinnamon.\n2. Dip bread slices.\n3. Cook on buttered pan until golden.\n4. Serve with maple syrup.',
|
|
ingredients: [
|
|
{ item_name: 'Bread', quantity: 4, unit: 'slices' },
|
|
{ item_name: 'Eggs', quantity: 2, unit: 'pieces' },
|
|
{ item_name: 'Milk', quantity: 0.25, unit: 'cups' },
|
|
{ item_name: 'Butter', quantity: 1, unit: 'tbsp' },
|
|
{ item_name: 'Cinnamon', quantity: 0.5, unit: 'tsp' },
|
|
],
|
|
},
|
|
{
|
|
name: 'Oatmeal',
|
|
servings: 1,
|
|
instructions: '1. Bring water or milk to boil.\n2. Add oats.\n3. Cook 5 minutes stirring occasionally.\n4. Top with fruit and honey.',
|
|
ingredients: [
|
|
{ item_name: 'Rolled Oats', quantity: 0.5, unit: 'cups' },
|
|
{ item_name: 'Milk', quantity: 1, unit: 'cups' },
|
|
{ item_name: 'Honey', quantity: 1, unit: 'tbsp' },
|
|
{ item_name: 'Salt', quantity: 1, unit: 'pinch' },
|
|
],
|
|
},
|
|
];
|
|
|
|
export async function seed(knex: Knex): Promise<void> {
|
|
// Clear existing
|
|
await knex('recipe_ingredients').delete();
|
|
await knex('recipes').delete();
|
|
|
|
for (const recipe of recipes) {
|
|
const [inserted] = await knex('recipes')
|
|
.insert({
|
|
name: recipe.name,
|
|
servings: recipe.servings,
|
|
instructions: recipe.instructions,
|
|
})
|
|
.returning('id');
|
|
|
|
const recipeId = inserted.id;
|
|
|
|
await knex('recipe_ingredients').insert(
|
|
recipe.ingredients.map((ing) => ({
|
|
recipe_id: recipeId,
|
|
item_name: ing.item_name,
|
|
item_name_lower: ing.item_name.toLowerCase(),
|
|
quantity: ing.quantity,
|
|
unit: ing.unit,
|
|
}))
|
|
);
|
|
}
|
|
}
|