feat: add PWA offline support for recipe pages
- Add service worker with caching for build assets, static files, images, and pages - Add IndexedDB storage for recipes (brief and full data) - Add offline-db API endpoint for bulk recipe download - Add offline sync button component in header - Add offline-shell page for direct navigation fallback - Pre-cache __data.json for client-side navigation - Add +page.ts universal load functions with IndexedDB fallback - Add PWA manifest and icons for installability - Update recipe page to handle missing data gracefully
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import type { BriefRecipeType, RecipeModelType } from '../../../../types/types';
|
||||
import { Recipe } from '../../../../models/Recipe';
|
||||
import { dbConnect } from '../../../../utils/db';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
await dbConnect();
|
||||
|
||||
// Fetch brief recipes (for lists/filtering)
|
||||
const briefRecipes = await Recipe.find(
|
||||
{},
|
||||
'name short_name tags category icon description season dateModified'
|
||||
).lean() as BriefRecipeType[];
|
||||
|
||||
// Fetch full recipes with populated base recipe references
|
||||
const fullRecipes = await Recipe.find({})
|
||||
.populate({
|
||||
path: 'ingredients.baseRecipeRef',
|
||||
select: 'short_name name ingredients translations',
|
||||
populate: {
|
||||
path: 'ingredients.baseRecipeRef',
|
||||
select: 'short_name name ingredients translations',
|
||||
populate: {
|
||||
path: 'ingredients.baseRecipeRef',
|
||||
select: 'short_name name ingredients translations'
|
||||
}
|
||||
}
|
||||
})
|
||||
.populate({
|
||||
path: 'instructions.baseRecipeRef',
|
||||
select: 'short_name name instructions translations',
|
||||
populate: {
|
||||
path: 'instructions.baseRecipeRef',
|
||||
select: 'short_name name instructions translations',
|
||||
populate: {
|
||||
path: 'instructions.baseRecipeRef',
|
||||
select: 'short_name name instructions translations'
|
||||
}
|
||||
}
|
||||
})
|
||||
.lean() as RecipeModelType[];
|
||||
|
||||
// Map populated refs to resolvedRecipe field (same as individual item endpoint)
|
||||
function mapBaseRecipeRefs(items: any[]): any[] {
|
||||
if (!items) return items;
|
||||
return items.map((item: any) => {
|
||||
if (item.type === 'reference' && item.baseRecipeRef) {
|
||||
const resolvedRecipe = { ...item.baseRecipeRef };
|
||||
|
||||
if (resolvedRecipe.ingredients) {
|
||||
resolvedRecipe.ingredients = mapBaseRecipeRefs(resolvedRecipe.ingredients);
|
||||
}
|
||||
if (resolvedRecipe.instructions) {
|
||||
resolvedRecipe.instructions = mapBaseRecipeRefs(resolvedRecipe.instructions);
|
||||
}
|
||||
|
||||
return { ...item, resolvedRecipe };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
const processedFullRecipes = fullRecipes.map((recipe) => {
|
||||
const processed = { ...recipe };
|
||||
if (processed.ingredients) {
|
||||
processed.ingredients = mapBaseRecipeRefs(processed.ingredients);
|
||||
}
|
||||
if (processed.instructions) {
|
||||
processed.instructions = mapBaseRecipeRefs(processed.instructions);
|
||||
}
|
||||
return processed;
|
||||
});
|
||||
|
||||
return json({
|
||||
brief: JSON.parse(JSON.stringify(briefRecipes)),
|
||||
full: JSON.parse(JSON.stringify(processedFullRecipes)),
|
||||
syncedAt: new Date().toISOString()
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user