Files
jeremy 8715589cbb feat(hobbs-organizer): v1.1.0 password-protected LAN-only admin for calendar links
- /admin: first-visit password setup (scrypt), signed 30-day session cookie,
  calendars add/remove/reorder/rename/color/hide, masked URLs, per-feed test,
  history (20) with restore; /photo-admin -> /admin#photos placeholder
- settings in local /data (settings.json + auth.json + history/), seeded once
  from the calendar secret; dashboard + health read from settings
- start.mjs drops privileges after reading secrets and runs a front proxy that
  stamps the real client IP; admin refuses non-LAN and proxied requests
- deploy: HOBBS_DATA_DIR=/var/lib/hobbs-organizer/data, image 1.1.0 (1.0.0 kept)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9NnVEico3WvFxLyjy1iW3
2026-11-04 18:55:00 -06:00

48 lines
1.9 KiB
TypeScript

import { addCalendarDays, organizerDate, readCalendar } from "../../_lib/calendar-feed";
import { readSettings } from "../../_lib/store";
export const dynamic = "force-dynamic";
export async function GET() {
const { calendars } = await readSettings();
const feeds = calendars.filter((calendar) => calendar.enabled);
if (!feeds.length) {
return Response.json({ items: [], calendarsAvailable: 0, calendarsTotal: 0, updatedAt: new Date().toISOString() }, { status: 503 });
}
const now = Date.now();
const windowStart = now - 45 * 24 * 60 * 60 * 1_000;
const horizon = now + 60 * 24 * 60 * 60 * 1_000;
const today = organizerDate(new Date(now));
const windowStartDate = addCalendarDays(today, -45);
const horizonDate = addCalendarDays(today, 60);
const results = await Promise.allSettled(
feeds.map((feed, feedIndex) =>
readCalendar(feed.url, { feedIndex, tone: feed.tone, nameOverride: feed.name }, windowStart, horizon, windowStartDate, horizonDate),
),
);
const successful = results.filter((result): result is PromiseFulfilledResult<Awaited<ReturnType<typeof readCalendar>>> => result.status === "fulfilled");
const items = successful
.flatMap((result) => result.value.items)
.filter((item, index, all) => all.findIndex((candidate) => candidate.id === item.id) === index)
.sort((left, right) => {
const leftValue = left.allDay ? `${left.start}T00:00:00` : left.start;
const rightValue = right.allDay ? `${right.start}T00:00:00` : right.start;
return leftValue.localeCompare(rightValue);
})
.slice(0, 160);
return Response.json(
{
items,
calendarsAvailable: successful.length,
calendarsTotal: feeds.length,
updatedAt: new Date().toISOString(),
},
{
status: successful.length ? 200 : 503,
headers: { "Cache-Control": "private, max-age=60, stale-while-revalidate=240" },
},
);
}