fix(errors): surface Bible verses on section error pages

SvelteKit's handleError hook is skipped for expected `error()` throws,
so verses set there never reached `$page.error` for server-thrown 404s
and auth denials. Introduce `errorWithVerse()` in `$lib/server/errorQuote`
that fetches a random verse first, then throws `error(status, body)`
with `{ message, bibleQuote, lang }`, making the quote available in
every `SectionError`. Convert all page load throws (catchalls, layout
validators, calendar, prayers, recipes, fitness, cospend, admin) and
hooks.server auth gates to the helper. Add `src/error.html` as a
branded last-resort fallback.
This commit is contained in:
2026-04-20 22:39:04 +02:00
parent fbd09fbdae
commit ad154bf914
33 changed files with 311 additions and 141 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "1.37.0", "version": "1.37.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+145
View File
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#5E81AC" />
<link rel="icon" href="/favicon.svg" />
<title>%sveltekit.status% — Error</title>
<script>
(function () {
try {
var t = localStorage.getItem('theme');
if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;
} catch (_) {}
})();
</script>
<style>
:root {
--bg: #f8f6f1;
--text: #2a2a2a;
--text-muted: #555;
--text-dim: #777;
--border: #ddd;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #000;
--text: #e5e5e5;
--text-muted: #aaa;
--text-dim: #888;
--border: #333;
}
}
:root[data-theme='light'] {
--bg: #f8f6f1;
--text: #2a2a2a;
--text-muted: #555;
--text-dim: #777;
--border: #ddd;
}
:root[data-theme='dark'] {
--bg: #000;
--text: #e5e5e5;
--text-muted: #aaa;
--text-dim: #888;
--border: #333;
}
html,
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: Helvetica, Arial, 'Noto Sans', sans-serif;
}
main {
min-height: 100vh;
display: flex;
align-items: flex-start;
justify-content: center;
padding: clamp(3rem, 10vh, 8rem) 1.5rem 4rem;
}
article {
width: 100%;
max-width: 640px;
}
.eyebrow {
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 1rem;
}
.code {
font-size: clamp(7rem, 22vw, 14rem);
font-weight: 200;
line-height: 0.9;
letter-spacing: -0.05em;
color: var(--text);
margin: 0 0 1.5rem;
font-variant-numeric: lining-nums tabular-nums;
}
.title {
font-size: clamp(1.5rem, 3vw, 2rem);
font-weight: 500;
letter-spacing: -0.01em;
margin: 0 0 0.5rem;
}
.description {
font-size: 1.0625rem;
line-height: 1.55;
color: var(--text-muted);
margin: 0;
max-width: 44ch;
}
.actions {
display: flex;
gap: 1.75rem;
margin: 2.25rem 0 0;
flex-wrap: wrap;
}
.link {
font-size: 0.95rem;
color: var(--text-muted);
text-decoration: none;
position: relative;
transition: color 200ms ease;
}
.link::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -2px;
height: 1px;
background: currentColor;
opacity: 0.35;
transition: opacity 200ms ease;
}
.link:hover {
color: var(--text);
}
.link:hover::after {
opacity: 1;
}
</style>
</head>
<body>
<main>
<article>
<div class="eyebrow">Error</div>
<div class="code" aria-hidden="true">%sveltekit.status%</div>
<h1 class="title">%sveltekit.error.message%</h1>
<p class="description">
An unexpected error occurred while rendering this page.
</p>
<nav class="actions">
<a class="link" href="/">Homepage</a>
<a class="link" href="javascript:history.back()">Go back</a>
</nav>
</article>
</main>
</body>
</html>
+27 -53
View File
@@ -1,10 +1,10 @@
import type { Handle, HandleServerError } from "@sveltejs/kit" import type { Handle, HandleServerError } from "@sveltejs/kit"
import { redirect } from "@sveltejs/kit" import { redirect } from "@sveltejs/kit"
import { error } from "@sveltejs/kit"
import { sequence } from "@sveltejs/kit/hooks" import { sequence } from "@sveltejs/kit/hooks"
import * as auth from "./auth" import * as auth from "./auth"
import { initializeScheduler } from "./lib/server/scheduler" import { initializeScheduler } from "./lib/server/scheduler"
import { dbConnect } from "./utils/db" import { dbConnect } from "./utils/db"
import { errorWithVerse, getRandomVerse } from "$lib/server/errorQuote"
// Initialize database connection on server startup // Initialize database connection on server startup
console.log('🚀 Server starting - initializing database connection...'); console.log('🚀 Server starting - initializing database connection...');
@@ -21,27 +21,26 @@ await dbConnect().then(() => {
async function authorization({ event, resolve }: Parameters<Handle>[0]) { async function authorization({ event, resolve }: Parameters<Handle>[0]) {
const session = await event.locals.auth(); const session = await event.locals.auth();
event.locals.session = session; event.locals.session = session;
const { fetch, url } = event;
// Protect rezepte routes // Protect rezepte routes
if (event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')) { if (url.pathname.startsWith('/rezepte/edit') || url.pathname.startsWith('/rezepte/add')) {
if (!session) { if (!session) {
// Preserve the original URL the user was trying to access const callbackUrl = encodeURIComponent(url.pathname + url.search);
const callbackUrl = encodeURIComponent(event.url.pathname + event.url.search);
redirect(303, `/login?callbackUrl=${callbackUrl}`); redirect(303, `/login?callbackUrl=${callbackUrl}`);
} }
else if (!session.user?.groups?.includes('rezepte_users')) { else if (!session.user?.groups?.includes('rezepte_users')) {
error(403, { await errorWithVerse(fetch, url.pathname, 403,
message: 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.' 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.');
});
} }
} }
// Protect cospend routes and API endpoints // Protect cospend routes and API endpoints
if (event.url.pathname.startsWith('/cospend') || event.url.pathname.startsWith('/expenses') || event.url.pathname.startsWith('/api/cospend')) { if (url.pathname.startsWith('/cospend') || url.pathname.startsWith('/expenses') || url.pathname.startsWith('/api/cospend')) {
if (!session) { if (!session) {
// Allow share-token access to shopping list routes // Allow share-token access to shopping list routes
const isShoppingRoute = event.url.pathname.startsWith('/cospend/list') || event.url.pathname.startsWith('/expenses/list') || event.url.pathname.startsWith('/api/cospend/list'); const isShoppingRoute = url.pathname.startsWith('/cospend/list') || url.pathname.startsWith('/expenses/list') || url.pathname.startsWith('/api/cospend/list');
const shareToken = event.url.searchParams.get('token'); const shareToken = url.searchParams.get('token');
if (isShoppingRoute && shareToken) { if (isShoppingRoute && shareToken) {
const { validateShareToken } = await import('$lib/server/shoppingAuth'); const { validateShareToken } = await import('$lib/server/shoppingAuth');
if (await validateShareToken(shareToken)) { if (await validateShareToken(shareToken)) {
@@ -50,49 +49,42 @@ async function authorization({ event, resolve }: Parameters<Handle>[0]) {
} }
// For API routes, return 401 instead of redirecting // For API routes, return 401 instead of redirecting
if (event.url.pathname.startsWith('/api/cospend')) { if (url.pathname.startsWith('/api/cospend')) {
error(401, { await errorWithVerse(fetch, url.pathname, 401,
message: 'Anmeldung erforderlich. Du musst angemeldet sein, um auf diesen Bereich zugreifen zu können.' 'Anmeldung erforderlich. Du musst angemeldet sein, um auf diesen Bereich zugreifen zu können.');
});
} }
// For page routes, redirect to login // For page routes, redirect to login
const callbackUrl = encodeURIComponent(event.url.pathname + event.url.search); const callbackUrl = encodeURIComponent(url.pathname + url.search);
redirect(303, `/login?callbackUrl=${callbackUrl}`); redirect(303, `/login?callbackUrl=${callbackUrl}`);
} }
else if (!session.user?.groups?.includes('cospend')) { else if (!session.user?.groups?.includes('cospend')) {
error(403, { await errorWithVerse(fetch, url.pathname, 403,
message: 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.' 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.');
});
} }
} }
// Protect tasks routes and API endpoints // Protect tasks routes and API endpoints
if (event.url.pathname.startsWith('/tasks') || event.url.pathname.startsWith('/api/tasks')) { if (url.pathname.startsWith('/tasks') || url.pathname.startsWith('/api/tasks')) {
if (!session) { if (!session) {
if (event.url.pathname.startsWith('/api/tasks')) { if (url.pathname.startsWith('/api/tasks')) {
error(401, { await errorWithVerse(fetch, url.pathname, 401, 'Anmeldung erforderlich.');
message: 'Anmeldung erforderlich.'
});
} }
const callbackUrl = encodeURIComponent(event.url.pathname + event.url.search); const callbackUrl = encodeURIComponent(url.pathname + url.search);
redirect(303, `/login?callbackUrl=${callbackUrl}`); redirect(303, `/login?callbackUrl=${callbackUrl}`);
} }
else if (!session.user?.groups?.includes('task_users')) { else if (!session.user?.groups?.includes('task_users')) {
error(403, { await errorWithVerse(fetch, url.pathname, 403,
message: 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.' 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich. Falls du glaubst, dass dies ein Fehler ist, wende dich bitte an Alexander.');
});
} }
} }
// Protect fitness routes and API endpoints // Protect fitness routes and API endpoints
if (event.url.pathname.startsWith('/fitness') || event.url.pathname.startsWith('/api/fitness')) { if (url.pathname.startsWith('/fitness') || url.pathname.startsWith('/api/fitness')) {
if (!session) { if (!session) {
if (event.url.pathname.startsWith('/api/fitness')) { if (url.pathname.startsWith('/api/fitness')) {
error(401, { await errorWithVerse(fetch, url.pathname, 401, 'Authentication required.');
message: 'Authentication required.'
});
} }
const callbackUrl = encodeURIComponent(event.url.pathname + event.url.search); const callbackUrl = encodeURIComponent(url.pathname + url.search);
redirect(303, `/login?callbackUrl=${callbackUrl}`); redirect(303, `/login?callbackUrl=${callbackUrl}`);
} }
} }
@@ -101,32 +93,14 @@ async function authorization({ event, resolve }: Parameters<Handle>[0]) {
return resolve(event); return resolve(event);
} }
// Bible verse functionality for error pages
async function getRandomVerse(fetch: typeof globalThis.fetch, pathname: string): Promise<{ text: string; reference: string } | null> {
const isEnglish = pathname.startsWith('/faith/') || pathname.startsWith('/recipes/');
const endpoint = isEnglish ? '/api/faith/bibel/zufallszitat' : '/api/glaube/bibel/zufallszitat';
try {
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (err) {
console.error('Error getting random verse:', err);
return null;
}
}
export const handleError: HandleServerError = async ({ error, event, status, message }) => { export const handleError: HandleServerError = async ({ error, event, status, message }) => {
console.error('Error occurred:', { error, status, message, url: event.url.pathname }); console.error('Error occurred:', { error, status, message, url: event.url.pathname });
// Add Bible verse to error context
const bibleQuote = await getRandomVerse(event.fetch, event.url.pathname);
const bibleQuote = await getRandomVerse(event.fetch, event.url.pathname);
const isEnglish = event.url.pathname.startsWith('/faith/') || event.url.pathname.startsWith('/recipes/'); const isEnglish = event.url.pathname.startsWith('/faith/') || event.url.pathname.startsWith('/recipes/');
return { return {
message: message, message,
bibleQuote, bibleQuote,
lang: isEnglish ? 'en' : 'de' lang: isEnglish ? 'en' : 'de'
}; };
+34
View File
@@ -0,0 +1,34 @@
import { error } from '@sveltejs/kit';
type Fetch = typeof globalThis.fetch;
function detectLang(pathname: string): 'en' | 'de' {
return pathname.startsWith('/faith/') || pathname.startsWith('/recipes/') ? 'en' : 'de';
}
export async function getRandomVerse(
fetch: Fetch,
pathname: string
): Promise<{ text: string; reference: string } | null> {
const endpoint =
detectLang(pathname) === 'en'
? '/api/faith/bibel/zufallszitat'
: '/api/glaube/bibel/zufallszitat';
try {
const res = await fetch(endpoint);
if (!res.ok) return null;
return await res.json();
} catch {
return null;
}
}
export async function errorWithVerse(
fetch: Fetch,
pathname: string,
status: number,
message = ''
): Promise<never> {
const bibleQuote = await getRandomVerse(fetch, pathname);
error(status, { message, bibleQuote, lang: detectLang(pathname) });
}
@@ -0,0 +1,5 @@
import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = ({ fetch, url }) =>
errorWithVerse(fetch, url.pathname, 404, 'Not found');
@@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export const load = () => {
error(404, 'Not found');
};
@@ -1,9 +1,10 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, fetch }) => { export const load: PageServerLoad = async ({ locals, fetch, url }) => {
const session = await locals.auth(); const session = await locals.auth();
if (!session) { if (!session) {
throw redirect(302, '/login'); throw redirect(302, '/login');
} }
@@ -14,11 +15,11 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
fetch('/api/cospend/balance'), fetch('/api/cospend/balance'),
fetch('/api/cospend/debts') fetch('/api/cospend/debts')
]); ]);
if (!balanceResponse.ok) { if (!balanceResponse.ok) {
throw new Error('Failed to fetch balance'); throw new Error('Failed to fetch balance');
} }
if (!debtResponse.ok) { if (!debtResponse.ok) {
throw new Error('Failed to fetch debt data'); throw new Error('Failed to fetch debt data');
} }
@@ -33,6 +34,6 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
}; };
} catch (e) { } catch (e) {
console.error('Error loading dashboard data:', e); console.error('Error loading dashboard data:', e);
throw error(500, 'Failed to load dashboard data'); await errorWithVerse(fetch, url.pathname, 500, 'Failed to load dashboard data');
} }
}; };
@@ -1,5 +1,6 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, fetch, url }) => { export const load: PageServerLoad = async ({ locals, fetch, url }) => {
const session = await locals.auth(); const session = await locals.auth();
@@ -29,6 +30,6 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
}; };
} catch (e) { } catch (e) {
console.error('Error loading payments data:', e); console.error('Error loading payments data:', e);
throw error(500, 'Failed to load payments data'); await errorWithVerse(fetch, url.pathname, 500, 'Failed to load payments data');
} }
}; };
@@ -1,7 +1,8 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, params, fetch }) => { export const load: PageServerLoad = async ({ locals, params, fetch, url }) => {
const session = await locals.auth(); const session = await locals.auth();
if (!session) { if (!session) {
@@ -23,6 +24,6 @@ export const load: PageServerLoad = async ({ locals, params, fetch }) => {
}; };
} catch (e) { } catch (e) {
console.error('Error loading payment data:', e); console.error('Error loading payment data:', e);
throw error(500, 'Failed to load payment data'); await errorWithVerse(fetch, url.pathname, 500, 'Failed to load payment data');
} }
}; };
@@ -1,13 +1,18 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores';
import SectionError from '$lib/components/SectionError.svelte'; import SectionError from '$lib/components/SectionError.svelte';
import { page } from '$app/stores';
let faithLang = $derived($page.params.faithLang); let faithLang = $derived($page.params.faithLang);
let isEnglish = $derived(faithLang === 'faith'); let isEnglish = $derived(faithLang === 'faith');
let sectionLabel = $derived(
faithLang === 'fides'
? { en: 'Fides', de: 'Fides' }
: { en: 'Faith', de: 'Glaube' }
);
</script> </script>
<SectionError <SectionError
sectionHref="/{faithLang}" sectionHref="/{faithLang}"
sectionLabel={{ en: 'Faith', de: 'Glaube' }} {sectionLabel}
{isEnglish} {isEnglish}
/> />
@@ -1,10 +1,10 @@
import type { LayoutServerLoad } from "./$types" import type { LayoutServerLoad } from "./$types"
import { error } from "@sveltejs/kit"; import { errorWithVerse } from "$lib/server/errorQuote"
export const load : LayoutServerLoad = async ({locals, params}) => { export const load : LayoutServerLoad = async ({locals, params, fetch, url}) => {
// Validate faithLang parameter // Validate faithLang parameter
if (params.faithLang !== 'glaube' && params.faithLang !== 'faith' && params.faithLang !== 'fides') { if (params.faithLang !== 'glaube' && params.faithLang !== 'faith' && params.faithLang !== 'fides') {
throw error(404, 'Not found'); await errorWithVerse(fetch, url.pathname, 404, 'Not found');
} }
const lang = params.faithLang === 'faith' ? 'en' : params.faithLang === 'fides' ? 'la' : 'de'; const lang = params.faithLang === 'faith' ? 'en' : params.faithLang === 'fides' ? 'la' : 'de';
@@ -0,0 +1,5 @@
import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = ({ fetch, url }) =>
errorWithVerse(fetch, url.pathname, 404, 'Not found');
@@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export const load = () => {
error(404, 'Not found');
};
@@ -1,10 +1,11 @@
import { error, redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { expectedSlug } from './calendarI18n'; import { expectedSlug } from './calendarI18n';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ params, url }) => { export const load: PageServerLoad = async ({ params, url, fetch }) => {
const slug = expectedSlug(params.faithLang); const slug = expectedSlug(params.faithLang);
if (slug === null) throw error(404, 'Not found'); if (slug === null) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
if (params.calendar !== slug) { if (params.calendar !== slug) {
throw redirect(307, `/${params.faithLang}/${slug}`); throw redirect(307, `/${params.faithLang}/${slug}`);
} }
@@ -1,5 +1,6 @@
import { error, redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
import { import {
DEFAULT_DIOCESE_1962, DEFAULT_DIOCESE_1962,
DEFAULT_DIOCESE_1969, DEFAULT_DIOCESE_1969,
@@ -29,9 +30,9 @@ export type {
YearDay YearDay
} from '$lib/calendarTypes'; } from '$lib/calendarTypes';
export const load: PageServerLoad = async ({ params, url, locals }) => { export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
const slug = expectedSlug(params.faithLang); const slug = expectedSlug(params.faithLang);
if (slug === null) throw error(404, 'Not found'); if (slug === null) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
if (params.calendar !== slug) { if (params.calendar !== slug) {
throw redirect(307, `/${params.faithLang}/${slug}`); throw redirect(307, `/${params.faithLang}/${slug}`);
} }
@@ -52,7 +53,7 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
// Reject mm without yyyy, dd without yyyy+mm. Sveltekit optional routes let // Reject mm without yyyy, dd without yyyy+mm. Sveltekit optional routes let
// gaps through so we normalize here. // gaps through so we normalize here.
if ((params.mm && !params.yyyy) || (params.dd && !params.mm)) { if ((params.mm && !params.yyyy) || (params.dd && !params.mm)) {
throw error(404, 'Not found'); await errorWithVerse(fetch, url.pathname, 404, 'Not found');
} }
const today = new Date(); const today = new Date();
@@ -92,7 +93,7 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
const daysInMonth = new Date(year, month + 1, 0).getDate(); const daysInMonth = new Date(year, month + 1, 0).getDate();
if (params.dd) { if (params.dd) {
const ddNum = Number(params.dd); const ddNum = Number(params.dd);
if (ddNum < 1 || ddNum > daysInMonth) throw error(404, 'Not found'); if (ddNum < 1 || ddNum > daysInMonth) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
} }
// Tentative selectedIso used only for the LY rollover decision. The real // Tentative selectedIso used only for the LY rollover decision. The real
// selectedIso is recomputed after monthDays below (same logic, now on the // selectedIso is recomputed after monthDays below (same logic, now on the
@@ -1,5 +1,6 @@
import { error, redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
import { import {
DEFAULT_DIOCESE_1962, DEFAULT_DIOCESE_1962,
DEFAULT_DIOCESE_1969, DEFAULT_DIOCESE_1969,
@@ -13,9 +14,9 @@ import {
} from '../../../../../calendarI18n'; } from '../../../../../calendarI18n';
import { getYear, getYear1962, isoFor } from '$lib/server/liturgicalCalendar'; import { getYear, getYear1962, isoFor } from '$lib/server/liturgicalCalendar';
export const load: PageServerLoad = async ({ params, url, locals }) => { export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
const slug = expectedSlug(params.faithLang); const slug = expectedSlug(params.faithLang);
if (slug === null) throw error(404, 'Not found'); if (slug === null) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
if (params.calendar !== slug) { if (params.calendar !== slug) {
throw redirect(307, `/${params.faithLang}/${slug}`); throw redirect(307, `/${params.faithLang}/${slug}`);
} }
@@ -38,10 +39,10 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
const month = Number(params.mm) - 1; const month = Number(params.mm) - 1;
const day = Number(params.dd); const day = Number(params.dd);
if (!Number.isFinite(year) || year < minYear || year > 2100) throw error(404, 'Not found'); if (!Number.isFinite(year) || year < minYear || year > 2100) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
if (!Number.isFinite(month) || month < 0 || month > 11) throw error(404, 'Not found'); if (!Number.isFinite(month) || month < 0 || month > 11) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
const daysInMonth = new Date(year, month + 1, 0).getDate(); const daysInMonth = new Date(year, month + 1, 0).getDate();
if (!Number.isFinite(day) || day < 1 || day > daysInMonth) throw error(404, 'Not found'); if (!Number.isFinite(day) || day < 1 || day > daysInMonth) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
const iso = isoFor(year, month, day); const iso = isoFor(year, month, day);
const yearMap = const yearMap =
@@ -49,7 +50,7 @@ export const load: PageServerLoad = async ({ params, url, locals }) => {
? await getYear1962(lang, diocese1962, year) ? await getYear1962(lang, diocese1962, year)
: await getYear(lang, diocese1969, year); : await getYear(lang, diocese1969, year);
const entry = yearMap.get(iso); const entry = yearMap.get(iso);
if (!entry) throw error(404, 'Not found'); if (!entry) await errorWithVerse(fetch, url.pathname, 404, 'Not found');
const today = new Date(); const today = new Date();
const todayIso = today.toISOString().slice(0, 10); const todayIso = today.toISOString().slice(0, 10);
@@ -1,12 +1,13 @@
import type { PageServerLoad, Actions } from "./$types"; import type { PageServerLoad, Actions } from "./$types";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { errorWithVerse } from '$lib/server/errorQuote';
import { validPrayerSlugs } from '$lib/data/prayerSlugs'; import { validPrayerSlugs } from '$lib/data/prayerSlugs';
const angelusSlugs = new Set(['angelus', 'regina-caeli']); const angelusSlugs = new Set(['angelus', 'regina-caeli']);
export const load: PageServerLoad = async ({ params, url, locals, fetch }) => { export const load: PageServerLoad = async ({ params, url, locals, fetch }) => {
if (!validPrayerSlugs.has(params.prayer)) { if (!validPrayerSlugs.has(params.prayer)) {
throw error(404, 'Prayer not found'); await errorWithVerse(fetch, url.pathname, 404, 'Prayer not found');
} }
const latinParam = url.searchParams.get('latin'); const latinParam = url.searchParams.get('latin');
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores';
import SectionError from '$lib/components/SectionError.svelte'; import SectionError from '$lib/components/SectionError.svelte';
import { page } from '$app/stores';
let recipeLang = $derived($page.params.recipeLang); let recipeLang = $derived($page.params.recipeLang);
let isEnglish = $derived(recipeLang === 'recipes'); let isEnglish = $derived(recipeLang === 'recipes');
@@ -1,10 +1,10 @@
import type { LayoutServerLoad } from "./$types" import type { LayoutServerLoad } from "./$types"
import { error } from "@sveltejs/kit"; import { errorWithVerse } from "$lib/server/errorQuote"
export const load : LayoutServerLoad = async ({locals, params}) => { export const load : LayoutServerLoad = async ({locals, params, fetch, url}) => {
// Validate recipeLang parameter // Validate recipeLang parameter
if (params.recipeLang !== 'rezepte' && params.recipeLang !== 'recipes') { if (params.recipeLang !== 'rezepte' && params.recipeLang !== 'recipes') {
throw error(404, 'Not found'); await errorWithVerse(fetch, url.pathname, 404, 'Not found');
} }
const lang = params.recipeLang === 'recipes' ? 'en' : 'de'; const lang = params.recipeLang === 'recipes' ? 'en' : 'de';
@@ -0,0 +1,5 @@
import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = ({ fetch, url }) =>
errorWithVerse(fetch, url.pathname, 404, 'Not found');
@@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export const load = () => {
error(404, 'Not found');
};
@@ -1,15 +1,16 @@
import { redirect, error } from '@sveltejs/kit'; import { redirect, error } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types'; import type { PageServerLoad, Actions } from './$types';
import { stripHtmlTags } from '$lib/js/stripHtmlTags'; import { stripHtmlTags } from '$lib/js/stripHtmlTags';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ fetch, params, locals }) => { export const load: PageServerLoad = async ({ fetch, params, locals, url }) => {
const isEnglish = params.recipeLang === 'recipes'; const isEnglish = params.recipeLang === 'recipes';
const apiBase = `/api/${params.recipeLang}`; const apiBase = `/api/${params.recipeLang}`;
const res = await fetch(`${apiBase}/items/${params.name}`); const res = await fetch(`${apiBase}/items/${params.name}`);
if (!res.ok) { if (!res.ok) {
throw error(res.status, 'Recipe not found'); await errorWithVerse(fetch, url.pathname, res.status, 'Recipe not found');
} }
const item = await res.json(); const item = await res.json();
@@ -1,7 +1,8 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, url }) => { export const load: PageServerLoad = async ({ locals, url, fetch }) => {
const session = await locals.auth(); const session = await locals.auth();
// Redirect to login if not authenticated // Redirect to login if not authenticated
@@ -12,7 +13,7 @@ export const load: PageServerLoad = async ({ locals, url }) => {
// Check user group permission // Check user group permission
if (!session.user.groups?.includes('rezepte_users')) { if (!session.user.groups?.includes('rezepte_users')) {
throw error(403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.'); await errorWithVerse(fetch, url.pathname, 403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.');
} }
return { return {
@@ -1,7 +1,8 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, url }) => { export const load: PageServerLoad = async ({ locals, url, fetch }) => {
const session = await locals.auth(); const session = await locals.auth();
if (!session?.user?.nickname) { if (!session?.user?.nickname) {
@@ -10,7 +11,7 @@ export const load: PageServerLoad = async ({ locals, url }) => {
} }
if (!session.user.groups?.includes('rezepte_users')) { if (!session.user.groups?.includes('rezepte_users')) {
throw error(403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.'); await errorWithVerse(fetch, url.pathname, 403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.');
} }
return { return {
@@ -1,5 +1,6 @@
import type { PageServerLoad } from "./$types"; import type { PageServerLoad } from "./$types";
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ fetch, locals, url, params }) => { export const load: PageServerLoad = async ({ fetch, locals, url, params }) => {
const session = await locals.auth(); const session = await locals.auth();
@@ -12,7 +13,7 @@ export const load: PageServerLoad = async ({ fetch, locals, url, params }) => {
// Check user group permission // Check user group permission
if (!session.user.groups?.includes('rezepte_users')) { if (!session.user.groups?.includes('rezepte_users')) {
throw error(403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.'); await errorWithVerse(fetch, url.pathname, 403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.');
} }
try { try {
@@ -1,7 +1,8 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { redirect, error } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ locals, params, url }) => { export const load: PageServerLoad = async ({ locals, params, url, fetch }) => {
const session = await locals.auth(); const session = await locals.auth();
// Redirect to login if not authenticated // Redirect to login if not authenticated
@@ -12,7 +13,7 @@ export const load: PageServerLoad = async ({ locals, params, url }) => {
// Check user group permission // Check user group permission
if (!session.user.groups?.includes('rezepte_users')) { if (!session.user.groups?.includes('rezepte_users')) {
throw error(403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.'); await errorWithVerse(fetch, url.pathname, 403, 'Zugriff verweigert. Du hast keine Berechtigung für diesen Bereich.');
} }
return { return {
@@ -0,0 +1,5 @@
import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = ({ fetch, url }) =>
errorWithVerse(fetch, url.pathname, 404, 'Not found');
-5
View File
@@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export const load = () => {
error(404, 'Not found');
};
@@ -1,5 +1,5 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ params, fetch, url }) => { export const load: PageServerLoad = async ({ params, fetch, url }) => {
const lang = url.pathname.includes('/uebungen') ? 'de' : 'en'; const lang = url.pathname.includes('/uebungen') ? 'de' : 'en';
@@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ params, fetch, url }) => {
]); ]);
if (!exerciseRes.ok) { if (!exerciseRes.ok) {
error(404, 'Exercise not found'); await errorWithVerse(fetch, url.pathname, 404, 'Exercise not found');
} }
const exerciseData = await exerciseRes.json(); const exerciseData = await exerciseRes.json();
@@ -1,11 +1,11 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = async ({ params, fetch }) => { export const load: PageServerLoad = async ({ params, fetch, url }) => {
const res = await fetch(`/api/fitness/sessions/${params.id}`); const res = await fetch(`/api/fitness/sessions/${params.id}`);
if (!res.ok) { if (!res.ok) {
error(404, 'Session not found'); await errorWithVerse(fetch, url.pathname, 404, 'Session not found');
} }
return { return {
@@ -1,5 +1,5 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit'; import { errorWithVerse } from '$lib/server/errorQuote';
import { NUTRITION_DB } from '$lib/data/nutritionDb'; import { NUTRITION_DB } from '$lib/data/nutritionDb';
import { BLS_DB } from '$lib/data/blsDb'; import { BLS_DB } from '$lib/data/blsDb';
import { DRI_MALE } from '$lib/data/dailyReferenceIntake'; import { DRI_MALE } from '$lib/data/dailyReferenceIntake';
@@ -60,16 +60,16 @@ async function computeRecipePer100g(id: string): Promise<Record<string, number>>
return per100g; return per100g;
} }
export const load: PageServerLoad = async ({ params, url }) => { export const load: PageServerLoad = async ({ params, url, fetch }) => {
const { source, id } = params; const { source, id } = params;
if (source !== 'bls' && source !== 'usda' && source !== 'recipe' && source !== 'off' && source !== 'custom') { if (source !== 'bls' && source !== 'usda' && source !== 'recipe' && source !== 'off' && source !== 'custom') {
throw error(404, 'Invalid source'); await errorWithVerse(fetch, url.pathname, 404, 'Invalid source');
} }
if (source === 'bls') { if (source === 'bls') {
const entry = BLS_DB.find(e => e.blsCode === id); const entry = BLS_DB.find(e => e.blsCode === id);
if (!entry) throw error(404, 'Food not found'); if (!entry) await errorWithVerse(fetch, url.pathname, 404, 'Food not found');
return { return {
food: { food: {
source: 'bls' as const, source: 'bls' as const,
@@ -91,7 +91,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
const recipe = await Recipe.findOne(recipeQuery) const recipe = await Recipe.findOne(recipeQuery)
.select('short_name name translations images') .select('short_name name translations images')
.lean(); .lean();
if (!recipe) throw error(404, 'Recipe not found'); if (!recipe) await errorWithVerse(fetch, url.pathname, 404, 'Recipe not found');
// Use logged per100g from food diary entry if provided, otherwise compute from current recipe // Use logged per100g from food diary entry if provided, otherwise compute from current recipe
const logEntryId = url.searchParams.get('logEntry'); const logEntryId = url.searchParams.get('logEntry');
@@ -131,7 +131,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
if (source === 'off') { if (source === 'off') {
await dbConnect(); await dbConnect();
const entry = await OpenFoodFact.findOne({ barcode: id }).lean(); const entry = await OpenFoodFact.findOne({ barcode: id }).lean();
if (!entry) throw error(404, 'Food not found'); if (!entry) await errorWithVerse(fetch, url.pathname, 404, 'Food not found');
const portions: { description: string; grams: number }[] = []; const portions: { description: string; grams: number }[] = [];
if (entry.serving?.grams) { if (entry.serving?.grams) {
portions.push(entry.serving as { description: string; grams: number }); portions.push(entry.serving as { description: string; grams: number });
@@ -156,7 +156,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
if (source === 'custom') { if (source === 'custom') {
await dbConnect(); await dbConnect();
const meal = await CustomMeal.findById(id).lean(); const meal = await CustomMeal.findById(id).lean();
if (!meal) throw error(404, 'Meal not found'); if (!meal) await errorWithVerse(fetch, url.pathname, 404, 'Meal not found');
// Aggregate per100g from ingredients // Aggregate per100g from ingredients
const totals: Record<string, number> = {}; const totals: Record<string, number> = {};
@@ -211,7 +211,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
// USDA // USDA
const fdcId = Number(id); const fdcId = Number(id);
const entry = NUTRITION_DB.find(e => e.fdcId === fdcId); const entry = NUTRITION_DB.find(e => e.fdcId === fdcId);
if (!entry) throw error(404, 'Food not found'); if (!entry) await errorWithVerse(fetch, url.pathname, 404, 'Food not found');
return { return {
food: { food: {
source: 'usda' as const, source: 'usda' as const,
@@ -0,0 +1,5 @@
import type { PageServerLoad } from './$types';
import { errorWithVerse } from '$lib/server/errorQuote';
export const load: PageServerLoad = ({ fetch, url }) =>
errorWithVerse(fetch, url.pathname, 404, 'Not found');
-5
View File
@@ -1,5 +0,0 @@
import { error } from '@sveltejs/kit';
export const load = () => {
error(404, 'Not found');
};