feat: add Latin route support, Angelus/Regina Caeli streak counter, and Eastertide liturgical adjustments
CI / update (push) Successful in 4m58s

- Add /fides route with Latin-only mode for all faith pages (rosary, prayers, individual prayers)
- Add LA option to language selector for faith routes
- Add Angelus/Regina Caeli streak counter with 3x daily tracking (morning/noon/evening bitmask)
- Store streak data in localStorage (offline) and MongoDB (logged-in sync)
- Show Annunciation/Coronation paintings via StickyImage with artist captions
- Switch Angelus↔Regina Caeli in header and landing page based on Eastertide
- Fix Eastertide to end at Ascension (+39 days) instead of Pentecost
- Fix Lent Holy Saturday off-by-one with toMidnight() normalization
- Fix non-reactive typedLang in faith layout
- Fix header nav highlighting: exclude angelus/regina-caeli from prayers active state
This commit is contained in:
2026-04-05 22:53:05 +02:00
parent c316cb533c
commit 6548ff5016
24 changed files with 1110 additions and 108 deletions
@@ -0,0 +1,73 @@
import { json, error, type RequestHandler } from '@sveltejs/kit';
import { AngelusStreak } from '$models/AngelusStreak';
import { dbConnect } from '$utils/db';
export const GET: RequestHandler = async ({ locals }) => {
const session = await locals.auth();
if (!session?.user?.nickname) {
throw error(401, 'Authentication required');
}
await dbConnect();
try {
const streak = await AngelusStreak.findOne({
username: session.user.nickname
}).lean() as any;
return json({
streak: streak?.streak ?? 0,
lastComplete: streak?.lastComplete ?? null,
todayPrayed: streak?.todayPrayed ?? 0,
todayDate: streak?.todayDate ?? null
});
} catch (e) {
throw error(500, 'Failed to fetch angelus streak');
}
};
export const POST: RequestHandler = async ({ request, locals }) => {
const session = await locals.auth();
if (!session?.user?.nickname) {
throw error(401, 'Authentication required');
}
const { streak, lastComplete, todayPrayed, todayDate } = await request.json();
if (typeof streak !== 'number' || streak < 0) {
throw error(400, 'Valid streak required');
}
if (lastComplete !== null && typeof lastComplete !== 'string') {
throw error(400, 'Invalid lastComplete format');
}
if (typeof todayPrayed !== 'number' || todayPrayed < 0 || todayPrayed > 7) {
throw error(400, 'Invalid todayPrayed bitmask');
}
if (todayDate !== null && typeof todayDate !== 'string') {
throw error(400, 'Invalid todayDate format');
}
await dbConnect();
try {
const updated = await AngelusStreak.findOneAndUpdate(
{ username: session.user.nickname },
{ streak, lastComplete, todayPrayed, todayDate },
{ upsert: true, new: true }
).lean() as any;
return json({
streak: updated?.streak ?? 0,
lastComplete: updated?.lastComplete ?? null,
todayPrayed: updated?.todayPrayed ?? 0,
todayDate: updated?.todayDate ?? null
});
} catch (e) {
throw error(500, 'Failed to update angelus streak');
}
};