Files
web-app-launcher/plans/phase-4-7-full-expansion/phase-2-widget-backend.md
T
alexei.dolgolyov 1c0a7cb850 feat: Phases 4-7 — Full Feature Expansion (26 features)
Phase 4 — New Widget Types:
- Clock/Weather, System Stats, RSS/Feed, Calendar, Markdown,
  Metric/Counter, Link Group, Camera/Stream widgets
- Backend services with caching for each data source
- Full creation form with dynamic config fields per type

Phase 5 — Visual & Styling Enhancements:
- Glassmorphism card style (solid/glass/outline)
- Board-level themes with per-board hue/saturation
- Animated SVG status rings replacing static dots
- Card size options (compact/medium/large)
- Custom CSS injection (admin + per-board, sanitized)
- Wallpaper backgrounds with blur/overlay/parallax

Phase 6 — Functional Features:
- Favorites bar with drag-and-drop reordering
- Recent apps tracking with privacy toggle
- Uptime dashboard page (/status, guest-accessible)
- Notifications system (Discord/Slack/Telegram/HTTP webhooks)
- App tags with filtering in board view
- Multi-URL app cards with expandable sub-links
- Personal API tokens with scoped permissions
- Audit log with retention and admin viewer

Phase 7 — Quality of Life:
- Onboarding wizard (5-step first-launch setup)
- App URL health preview with favicon/title detection
- Board templates (4 built-in + custom import/export)
- Keyboard shortcut overlay (j/k nav, 1-9 boards, ? help)

212 files changed, 15641 insertions, 980 deletions.
Build, lint, type check, and 222 tests all pass.
2026-03-25 14:18:10 +03:00

153 lines
7.9 KiB
Markdown

# Phase 2: New Widget Services & APIs
**Status:** ✅ Complete
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Implement backend services and API routes for all 8 new widget types. Each widget type that fetches external data needs a dedicated service for data fetching, caching, and error handling.
## Tasks
### 2.1 Clock/Weather service
- [x] Create `src/lib/server/services/weatherService.ts`
- Fetch weather from OpenMeteo API (no API key required): `https://api.open-meteo.com/v1/forecast`
- Accept latitude/longitude, return current temp, condition, icon
- Cache responses for 30 minutes (in-memory Map with TTL)
- Graceful fallback when API is unreachable
- [x] Create `src/routes/api/widgets/weather/+server.ts` — GET endpoint with lat/lng query params
### 2.2 System Stats service
- [x] Create `src/lib/server/services/systemStatsService.ts`
- Adapter pattern: `TrueNasAdapter`, `GlancesAdapter`, `CustomAdapter`
- Each adapter fetches metrics (CPU, RAM, disk) from its source URL
- Return normalized `{ metric: string, value: number, unit: string }[]`
- Configurable refresh interval, in-memory cache per source URL
- [x] Create `src/routes/api/widgets/system-stats/+server.ts` — GET with sourceUrl, sourceType params
### 2.3 RSS/Feed service
- [x] Create `src/lib/server/services/rssFeedService.ts`
- Fetch and parse RSS/Atom feeds (use built-in XML parsing or lightweight lib)
- Return `{ title, link, pubDate, summary }[]` limited to maxItems
- Cache feeds for 15 minutes per URL
- Handle malformed feeds gracefully
- [x] Create `src/routes/api/widgets/rss/+server.ts` — GET with feedUrl, maxItems params
### 2.4 Calendar service
- [x] Create `src/lib/server/services/calendarService.ts`
- Fetch and parse iCal (.ics) files from URLs
- Extract events within daysAhead range
- Return `{ summary, start, end, location?, calendarLabel, calendarColor }[]`
- Cache per URL for 30 minutes
- Handle multiple calendar URLs, merge and sort by start time
- [x] Create `src/routes/api/widgets/calendar/+server.ts` — POST with icalUrls[], daysAhead
### 2.5 Metric/Counter service
- [x] Create `src/lib/server/services/metricService.ts`
- `fetchHttpMetric(url, jsonPath)` — fetch JSON endpoint, extract value via JSONPath
- `fetchPrometheusMetric(url, query)` — query Prometheus API, extract instant value
- `getStaticMetric(value)` — passthrough for static values
- Store previous value for trend calculation (up/down/flat)
- Cache per source for configurable interval
- [x] Create `src/routes/api/widgets/metric/+server.ts` — GET with source type params
### 2.6 Camera/Stream proxy
- [x] Create `src/lib/server/services/cameraService.ts`
- `fetchSnapshot(url)` — proxy HTTP request to camera URL, return image buffer
- Validate URL is http/https only
- Timeout after 10s
- Rate limit: max 1 request per 5s per URL
- [x] Create `src/routes/api/widgets/camera/+server.ts` — GET with streamUrl param, returns proxied image
### 2.7 Widget data aggregation endpoint
- [x] Create `src/routes/api/widgets/data/+server.ts` — generic endpoint that routes to the correct service based on widget type and config
- POST with `{ widgetType, config }` body
- Routes to weatherService, systemStatsService, etc. based on type
- Returns unified response format
### 2.8 Update existing widget service
- [x] Update `src/lib/server/services/boardService.ts` to handle new widget types in create/update operations
- [x] Ensure new widget type configs are validated with the correct Zod schema on create/update
## Files to Modify/Create
- `src/lib/server/services/weatherService.ts` — new
- `src/lib/server/services/systemStatsService.ts` — new
- `src/lib/server/services/rssFeedService.ts` — new
- `src/lib/server/services/calendarService.ts` — new
- `src/lib/server/services/metricService.ts` — new
- `src/lib/server/services/cameraService.ts` — new
- `src/routes/api/widgets/weather/+server.ts` — new
- `src/routes/api/widgets/system-stats/+server.ts` — new
- `src/routes/api/widgets/rss/+server.ts` — new
- `src/routes/api/widgets/calendar/+server.ts` — new
- `src/routes/api/widgets/metric/+server.ts` — new
- `src/routes/api/widgets/camera/+server.ts` — new
- `src/routes/api/widgets/data/+server.ts` — new
- `src/lib/server/services/boardService.ts` — modify
## Acceptance Criteria
- Each service handles errors gracefully (network failures, malformed responses, timeouts)
- In-memory caching prevents excessive external API calls
- All API routes use consistent envelope response format
- All user inputs validated with Zod schemas from Phase 1
- Camera proxy validates URLs and prevents SSRF (allowlist http/https, no private IPs)
- Services are stateless (cache is ephemeral, no DB state needed for widget data)
## Notes
- OpenMeteo API is free, no key needed: `https://api.open-meteo.com/v1/forecast?latitude=X&longitude=Y&current_weather=true`
- For RSS parsing, consider using a lightweight approach (DOMParser or regex) to avoid adding a heavy dependency. If needed, `fast-xml-parser` is a good lightweight option.
- iCal parsing: use `node-ical` or hand-parse VEVENT blocks
- JSONPath extraction: use simple dot-notation traversal rather than a full JSONPath library
- SSRF protection for camera proxy: reject private IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1)
## Review Checklist
- [x] All tasks completed
- [x] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes (Big Bang: code quality check only)
- [ ] Tests pass (Big Bang: skipped for intermediate phase)
## Handoff to Next Phase
### What was done
- Created 6 new backend services: weatherService, systemStatsService, rssFeedService, calendarService, metricService, cameraService
- Created 7 new API routes under `/api/widgets/`: weather, system-stats, rss, calendar, metric, camera, data (aggregation)
- Updated boardService to validate widget configs against Zod schemas on create/update
- Updated boardService to pass through new theme/visual fields (themeHue, themeSaturation, backgroundType, cardSize, wallpaperUrl, wallpaperBlur, wallpaperOverlay, customCss) and section cardSize
- All services use in-memory caching with TTL (Map + expiry timestamps)
- Camera proxy includes SSRF protection (blocks private IPs, localhost, link-local) and rate limiting (1 req/5s per URL)
- RSS service uses lightweight regex-based XML parsing (no external dependency)
- Calendar service uses hand-parsed VEVENT blocks from iCal text (no external dependency)
- Metric service supports dot-notation JSONPath extraction (no external dependency)
### What the next phase needs to know
- All widget data endpoints follow the pattern: `/api/widgets/{type}` with GET (or POST for calendar)
- The aggregation endpoint `/api/widgets/data` accepts POST with `{ widgetType, config }` and routes to the correct service
- Camera endpoint returns raw image binary (not JSON envelope) for direct `<img>` src usage
- Markdown and LinkGroup widget types return no-op from the aggregation endpoint (they are client-side only)
- Clock widget without weather enabled also returns no-op (time is client-side)
- All services export a `clearCache()` function for testing/manual refresh
- The `validateStreamUrl()` function on cameraService is exported for reuse (used by aggregation endpoint)
### Potential concerns
- RSS/Atom XML parsing uses regex, which handles common feeds but may fail on exotic feed formats. If issues arise, consider adding `fast-xml-parser` as a dependency.
- iCal parsing handles standard VEVENT blocks but does not support RRULE (recurring events). A future enhancement could add recurrence expansion.
- SSRF protection checks IP format only at the URL level — DNS rebinding attacks could bypass hostname checks. For production hardening, consider resolving DNS before connecting.
- System stats adapters assume specific API shapes for Glances and Prometheus. Custom adapter is a generic JSON fallback.