diff --git a/SOCIAL_CALENDAR_PLAN.md b/SOCIAL_CALENDAR_PLAN.md index 11e7e91b..a5f07d9a 100644 --- a/SOCIAL_CALENDAR_PLAN.md +++ b/SOCIAL_CALENDAR_PLAN.md @@ -459,14 +459,15 @@ GET /api/admin/calendar/shared/:id/items — merged system-layer data for mat ### Phase C: .ics Integration **Scope:** -- [ ] Prisma models: CalendarFeed, CalendarExportToken -- [ ] .ics feed parser (node-ical or similar) -- [ ] BullMQ job: refresh feeds on configured intervals -- [ ] Feed CRUD: subscribe, update, delete, force refresh -- [ ] Auto-create layer per feed, cache items as CalendarItem rows -- [ ] .ics export: generate feed from user's calendar, token-authenticated URL -- [ ] Export token management (create, revoke) -- [ ] CalendarFeedsPanel, CalendarExportPanel components +- [x] Prisma models: CalendarFeed, CalendarExportToken (already existed from Phase A migration) +- [x] .ics feed parser (node-ical v0.25.5) +- [x] BullMQ job: refresh feeds every 15 minutes (calendar-feed-refresh queue) +- [x] Feed CRUD: subscribe, update, delete, force refresh +- [x] Auto-create EXTERNAL layer per feed, cache items as CalendarItem rows (sourceType: ICS_FEED) +- [x] .ics export: generate feed from user's calendar via ical-generator v10, token-authenticated URL +- [x] Export token management (create, list, revoke) +- [x] CalendarFeedsPanel, CalendarExportPanel components +- [x] MyCalendarPage settings Drawer integration (gear icon) ### Phase D: Admin Shared Views **Scope:** @@ -554,3 +555,15 @@ The existing `UnifiedCalendar` component and `unified-calendar.service.ts` remai - Smoke tested: layers auto-create, item CRUD works, recurring events materialize correctly (Weekly Mon/Wed/Fri generated 11 instances through June) - Both API and Admin compile with zero TypeScript errors - Remaining Phase A item: BullMQ job for extending recurring series (not critical for launch, series materializes 3 months on creation) + +### 2026-03-07 — Phase C Implementation Complete +- Backend: feed.schemas.ts (3 Zod schemas), feed.service.ts (feed CRUD, ICS parsing, RRULE materialization, export generation), feed.routes.ts (1 public + 8 auth routes), calendar-feed-queue.service.ts (BullMQ 15min repeatable job) +- Dependencies: node-ical v0.25.5 (ICS parsing), ical-generator v10.0.0 (ICS output) +- Feed import: streaming body read with 5MB limit, 1000 event cap, RRULE materialization via rrule.between(), stale event cleanup, status tracking (OK/ERROR/PENDING) +- Feed export: 32-byte random token, configurable layer/personal inclusion, past 1 month + future 3 months, standard iCalendar output with Content-Type: text/calendar +- Frontend: CalendarFeedsPanel (add/edit/delete/refresh with status badges), CalendarExportPanel (create/copy/revoke tokens), settings Drawer in MyCalendarPage (gear icon) +- Types: CalendarFeed, CalendarExportToken, CalendarFeedStatus, CalendarFeedInterval added to admin/src/types/api.ts +- server.ts: feedRoutes mounted before calendarRoutes (public .ics route needs no auth), queue worker started on bootstrap, graceful shutdown +- Smoke tested: Google US Holidays feed → 317 events imported with status OK; export token → valid .ics with VEVENT entries; revoke → 404 +- Docker gotcha: anonymous volume `/app/node_modules` caches old dependencies — must `docker compose rm -sf api` to clear when adding new npm packages +- Both API and Admin compile with zero TypeScript errors diff --git a/admin/public/auth-check.html b/admin/public/auth-check.html new file mode 100644 index 00000000..917e4931 --- /dev/null +++ b/admin/public/auth-check.html @@ -0,0 +1,28 @@ + +
| + {dates.map((date) => ( + | + {dayjs(date).format('ddd M/D')} + | + ))} +
|---|---|
| + {time} + | + {dates.map((date) => { + const dayData = availability.dates[date]; + const slot = dayData?.slots.find((s) => s.time === time); + const allFree = slot?.allFree; + + return ( + allFree && onSlotClick?.(date, time)}
+ style={{
+ padding: '3px 6px',
+ border: `1px solid ${token.colorBorderSecondary}`,
+ background: allFree
+ ? 'rgba(82, 196, 26, 0.15)'
+ : 'transparent',
+ cursor: allFree ? 'pointer' : 'default',
+ textAlign: 'center',
+ }}
+ >
+ {slot && (
+ |
+ );
+ })}
+