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

7.9 KiB

Phase 2: New Widget Services & APIs

Status: Complete Parent plan: 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

  • 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
  • Create src/routes/api/widgets/weather/+server.ts — GET endpoint with lat/lng query params

2.2 System Stats service

  • 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
  • Create src/routes/api/widgets/system-stats/+server.ts — GET with sourceUrl, sourceType params

2.3 RSS/Feed service

  • 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
  • Create src/routes/api/widgets/rss/+server.ts — GET with feedUrl, maxItems params

2.4 Calendar service

  • 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
  • Create src/routes/api/widgets/calendar/+server.ts — POST with icalUrls[], daysAhead

2.5 Metric/Counter service

  • 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
  • Create src/routes/api/widgets/metric/+server.ts — GET with source type params

2.6 Camera/Stream proxy

  • 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
  • Create src/routes/api/widgets/camera/+server.ts — GET with streamUrl param, returns proxied image

2.7 Widget data aggregation endpoint

  • 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

  • Update src/lib/server/services/boardService.ts to handle new widget types in create/update operations
  • 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

  • All tasks completed
  • 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.